epicmerch-mcp 1.2.0 → 1.3.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/package.json +1 -1
- package/skills/epicmerch-orders.md +17 -9
- package/skills/epicmerch-payments.md +86 -86
- package/skills/epicmerch-storefront.md +389 -0
- package/skills/epicmerch.md +50 -354
- package/src/prompts/index.js +158 -13
- package/src/tools/scaffold.js +17 -10
package/package.json
CHANGED
|
@@ -39,7 +39,8 @@ export default function Cart({ onCheckout }) {
|
|
|
39
39
|
|
|
40
40
|
const remove = async (productId) => {
|
|
41
41
|
await store.cart.remove(productId);
|
|
42
|
-
|
|
42
|
+
// Optimistic local update — no need to re-fetch the whole cart.
|
|
43
|
+
setCart(c => ({ ...c, items: (c.items || []).filter(i => i.productId !== productId) }));
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
const total = cart.items?.reduce((s, i) => s + i.price * i.qty, 0) ?? 0;
|
|
@@ -84,22 +85,29 @@ export default function Checkout({ cart, onSuccess }) {
|
|
|
84
85
|
const placeOrder = async () => {
|
|
85
86
|
await loadRazorpay();
|
|
86
87
|
const total = cart.items.reduce((s, i) => s + i.price * i.qty, 0);
|
|
87
|
-
|
|
88
|
+
|
|
89
|
+
// ONE call to /customer/orders is enough — the server creates the
|
|
90
|
+
// EpicMerch order AND the Razorpay order in a single transaction and
|
|
91
|
+
// returns both back. Do NOT separately call store.payment.createOrder
|
|
92
|
+
// here; doing so creates an orphan second Razorpay order AND can
|
|
93
|
+
// trigger an idempotency-key collision against /customer/orders.
|
|
94
|
+
const orderResult = await store.orders.create({
|
|
88
95
|
orderItems: cart.items.map(i => ({ productId: i.productId, qty: i.qty, price: i.price })),
|
|
89
96
|
shippingAddress: address,
|
|
90
97
|
paymentMethod: 'Razorpay',
|
|
91
98
|
totalPrice: total,
|
|
92
99
|
});
|
|
93
|
-
|
|
94
|
-
const { razorpayOrderId } = await store.payment.createOrder(total * 100, orderId);
|
|
100
|
+
|
|
95
101
|
const rzp = new window.Razorpay({
|
|
96
|
-
key:
|
|
97
|
-
order_id: razorpayOrderId,
|
|
98
|
-
amount:
|
|
102
|
+
key: orderResult.razorpayKeyId,
|
|
103
|
+
order_id: orderResult.razorpayOrderId,
|
|
104
|
+
amount: orderResult.amount,
|
|
105
|
+
currency: orderResult.currency,
|
|
106
|
+
name: orderResult.merchantName,
|
|
99
107
|
handler: async (response) => {
|
|
100
|
-
await store.payment.verify({ ...response, orderId });
|
|
108
|
+
await store.payment.verify({ ...response, orderId: orderResult.orderId });
|
|
101
109
|
await store.cart.clear();
|
|
102
|
-
onSuccess(orderId);
|
|
110
|
+
onSuccess(orderResult.orderId);
|
|
103
111
|
},
|
|
104
112
|
});
|
|
105
113
|
rzp.open();
|
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: epicmerch-payments
|
|
3
|
-
description: Set up payments on an EpicMerch store — help the merchant pick between Razorpay (India, default) and Stripe (international), then route to the right setup skill.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# EpicMerch Payments — the umbrella
|
|
7
|
-
|
|
8
|
-
This is the entry point for any "set up payments" / "configure checkout" / "add card payments" intent. Your job is to figure out **which processor the merchant should use**, then hand off to the matching detailed skill.
|
|
9
|
-
|
|
10
|
-
## Step 1: Check what's already configured
|
|
11
|
-
|
|
12
|
-
Before asking anything, call `merchant_get_checkout_settings()`. The response tells you:
|
|
13
|
-
|
|
14
|
-
- `checkoutType` — current active flow (`"epicmerch"`, `"stripe"`, or `"shiprocket"`)
|
|
15
|
-
- `razorpayKeyId` (string or null) + `razorpayKeySecretIsSet` (bool)
|
|
16
|
-
- `stripePublishableKey` (string or null) + `stripeSecretKeyIsSet` (bool) + `stripeWebhookSecretIsSet` (bool)
|
|
17
|
-
|
|
18
|
-
Decide based on what's there:
|
|
19
|
-
|
|
20
|
-
| Current state | Suggested action |
|
|
21
|
-
|---|---|
|
|
22
|
-
| Nothing configured | Help the merchant pick + set up (continue to Step 2) |
|
|
23
|
-
| Razorpay configured, `checkoutType: epicmerch` | Tell them: "Razorpay is already live on your store. Want to add Stripe as a backup / switch to Stripe?" |
|
|
24
|
-
| Stripe configured, `checkoutType: stripe` | Tell them: "Stripe is already live. Want to switch back to Razorpay?" |
|
|
25
|
-
| Both configured, only one active | Tell them which is active + ask if they want to switch |
|
|
26
|
-
|
|
27
|
-
If something's clearly already set up and they didn't ask to change it, stop here. Don't redo configured work.
|
|
28
|
-
|
|
29
|
-
## Step 2: Help them pick
|
|
30
|
-
|
|
31
|
-
If nothing is configured (or they want to add a new one), help them choose:
|
|
32
|
-
|
|
33
|
-
| Question | Razorpay | Stripe |
|
|
34
|
-
|---|---|---|
|
|
35
|
-
| Where are most of your customers? | India 🇮🇳 | International 🌍 |
|
|
36
|
-
| Currency you charge in? | INR primarily | USD, EUR, GBP, AUD, etc. |
|
|
37
|
-
| Payment methods you want? | UPI, netbanking, Indian cards, wallets | Cards, Apple Pay, Google Pay, Stripe Link |
|
|
38
|
-
| Compliance | RBI-compliant by default | Stripe handles PCI; you handle GDPR |
|
|
39
|
-
| Setup time | ~5 min if account exists | ~10 min (needs webhook config) |
|
|
40
|
-
| Account onboarding | KYC ~24h (already done usually) | Stripe activation can take a day |
|
|
41
|
-
|
|
42
|
-
Ask the merchant ONE question that disambiguates. Don't make them read the table. The best single question is usually:
|
|
43
|
-
|
|
44
|
-
> "Where are most of your customers — India, or international?"
|
|
45
|
-
|
|
46
|
-
- **India only** → Razorpay
|
|
47
|
-
- **International / mostly outside India** → Stripe
|
|
48
|
-
- **Both / not sure** → ask: "What currency do you want to charge in?" then route accordingly.
|
|
49
|
-
|
|
50
|
-
## Step 3: Hand off to the specific skill
|
|
51
|
-
|
|
52
|
-
Once you know which one:
|
|
53
|
-
|
|
54
|
-
- **Razorpay** → hand off to `/epicmerch-razorpay`. Don't try to configure it inline; the specific skill has the right Razorpay-dashboard URLs, key format hints, and the activation step.
|
|
55
|
-
- **Stripe** → hand off to `/epicmerch-stripe`. Same reasoning — that skill has the webhook URL, the test recipe, and the activation step.
|
|
56
|
-
|
|
57
|
-
If you're running inside a client that doesn't support slash command handoff (e.g. **Codex**), invoke the same MCP tools yourself:
|
|
58
|
-
|
|
59
|
-
**For Razorpay inline:**
|
|
60
|
-
```
|
|
61
|
-
merchant_configure_razorpay({ keyId: "rzp_live_...", keySecret: "..." })
|
|
62
|
-
merchant_set_checkout_type({ type: "epicmerch" })
|
|
63
|
-
merchant_get_checkout_settings() // verify
|
|
64
|
-
merchant_diagnose() // confirm readiness
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**For Stripe inline:**
|
|
68
|
-
```
|
|
69
|
-
merchant_configure_stripe({ publishableKey: "pk_live_...", secretKey: "sk_live_...", webhookSecret: "whsec_..." })
|
|
70
|
-
merchant_test_stripe_connection()
|
|
71
|
-
merchant_set_checkout_type({ type: "stripe" })
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Step 4: After setup
|
|
75
|
-
|
|
76
|
-
Once whichever processor is live, suggest:
|
|
77
|
-
|
|
78
|
-
- "Want to test? Place a small test order on your storefront. With test keys, no real money moves; with live keys, you can refund it after."
|
|
79
|
-
- "Need to set up Shiprocket for shipping? Use `merchant_get_logistics_settings` first to see what's there."
|
|
80
|
-
- "Ready to launch? Run `merchant_diagnose()` — I'll check everything."
|
|
81
|
-
|
|
82
|
-
## Style
|
|
83
|
-
|
|
84
|
-
- Don't ask "Razorpay or Stripe?" out of context. ALWAYS check current state first (Step 1) — if it's set up, just confirm.
|
|
85
|
-
- Don't pile up questions. One at a time.
|
|
86
|
-
- Never paste the merchant's secret back at them — refer to it as `"the secret you provided"`. Treat secrets as write-only.
|
|
1
|
+
---
|
|
2
|
+
name: epicmerch-payments
|
|
3
|
+
description: Set up payments on an EpicMerch store — help the merchant pick between Razorpay (India, default) and Stripe (international), then route to the right setup skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# EpicMerch Payments — the umbrella
|
|
7
|
+
|
|
8
|
+
This is the entry point for any "set up payments" / "configure checkout" / "add card payments" intent. Your job is to figure out **which processor the merchant should use**, then hand off to the matching detailed skill.
|
|
9
|
+
|
|
10
|
+
## Step 1: Check what's already configured
|
|
11
|
+
|
|
12
|
+
Before asking anything, call `merchant_get_checkout_settings()`. The response tells you:
|
|
13
|
+
|
|
14
|
+
- `checkoutType` — current active flow (`"epicmerch"`, `"stripe"`, or `"shiprocket"`)
|
|
15
|
+
- `razorpayKeyId` (string or null) + `razorpayKeySecretIsSet` (bool)
|
|
16
|
+
- `stripePublishableKey` (string or null) + `stripeSecretKeyIsSet` (bool) + `stripeWebhookSecretIsSet` (bool)
|
|
17
|
+
|
|
18
|
+
Decide based on what's there:
|
|
19
|
+
|
|
20
|
+
| Current state | Suggested action |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Nothing configured | Help the merchant pick + set up (continue to Step 2) |
|
|
23
|
+
| Razorpay configured, `checkoutType: epicmerch` | Tell them: "Razorpay is already live on your store. Want to add Stripe as a backup / switch to Stripe?" |
|
|
24
|
+
| Stripe configured, `checkoutType: stripe` | Tell them: "Stripe is already live. Want to switch back to Razorpay?" |
|
|
25
|
+
| Both configured, only one active | Tell them which is active + ask if they want to switch |
|
|
26
|
+
|
|
27
|
+
If something's clearly already set up and they didn't ask to change it, stop here. Don't redo configured work.
|
|
28
|
+
|
|
29
|
+
## Step 2: Help them pick
|
|
30
|
+
|
|
31
|
+
If nothing is configured (or they want to add a new one), help them choose:
|
|
32
|
+
|
|
33
|
+
| Question | Razorpay | Stripe |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Where are most of your customers? | India 🇮🇳 | International 🌍 |
|
|
36
|
+
| Currency you charge in? | INR primarily | USD, EUR, GBP, AUD, etc. |
|
|
37
|
+
| Payment methods you want? | UPI, netbanking, Indian cards, wallets | Cards, Apple Pay, Google Pay, Stripe Link |
|
|
38
|
+
| Compliance | RBI-compliant by default | Stripe handles PCI; you handle GDPR |
|
|
39
|
+
| Setup time | ~5 min if account exists | ~10 min (needs webhook config) |
|
|
40
|
+
| Account onboarding | KYC ~24h (already done usually) | Stripe activation can take a day |
|
|
41
|
+
|
|
42
|
+
Ask the merchant ONE question that disambiguates. Don't make them read the table. The best single question is usually:
|
|
43
|
+
|
|
44
|
+
> "Where are most of your customers — India, or international?"
|
|
45
|
+
|
|
46
|
+
- **India only** → Razorpay
|
|
47
|
+
- **International / mostly outside India** → Stripe
|
|
48
|
+
- **Both / not sure** → ask: "What currency do you want to charge in?" then route accordingly.
|
|
49
|
+
|
|
50
|
+
## Step 3: Hand off to the specific skill
|
|
51
|
+
|
|
52
|
+
Once you know which one:
|
|
53
|
+
|
|
54
|
+
- **Razorpay** → hand off to `/epicmerch-razorpay`. Don't try to configure it inline; the specific skill has the right Razorpay-dashboard URLs, key format hints, and the activation step.
|
|
55
|
+
- **Stripe** → hand off to `/epicmerch-stripe`. Same reasoning — that skill has the webhook URL, the test recipe, and the activation step.
|
|
56
|
+
|
|
57
|
+
If you're running inside a client that doesn't support slash command handoff (e.g. **Codex**), invoke the same MCP tools yourself:
|
|
58
|
+
|
|
59
|
+
**For Razorpay inline:**
|
|
60
|
+
```
|
|
61
|
+
merchant_configure_razorpay({ keyId: "rzp_live_...", keySecret: "..." })
|
|
62
|
+
merchant_set_checkout_type({ type: "epicmerch" })
|
|
63
|
+
merchant_get_checkout_settings() // verify
|
|
64
|
+
merchant_diagnose() // confirm readiness
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**For Stripe inline:**
|
|
68
|
+
```
|
|
69
|
+
merchant_configure_stripe({ publishableKey: "pk_live_...", secretKey: "sk_live_...", webhookSecret: "whsec_..." })
|
|
70
|
+
merchant_test_stripe_connection()
|
|
71
|
+
merchant_set_checkout_type({ type: "stripe" })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Step 4: After setup
|
|
75
|
+
|
|
76
|
+
Once whichever processor is live, suggest:
|
|
77
|
+
|
|
78
|
+
- "Want to test? Place a small test order on your storefront. With test keys, no real money moves; with live keys, you can refund it after."
|
|
79
|
+
- "Need to set up Shiprocket for shipping? Use `merchant_get_logistics_settings` first to see what's there."
|
|
80
|
+
- "Ready to launch? Run `merchant_diagnose()` — I'll check everything."
|
|
81
|
+
|
|
82
|
+
## Style
|
|
83
|
+
|
|
84
|
+
- Don't ask "Razorpay or Stripe?" out of context. ALWAYS check current state first (Step 1) — if it's set up, just confirm.
|
|
85
|
+
- Don't pile up questions. One at a time.
|
|
86
|
+
- Never paste the merchant's secret back at them — refer to it as `"the secret you provided"`. Treat secrets as write-only.
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: epicmerch-storefront
|
|
3
|
+
description: One-shot full EpicMerch storefront scaffold — SDK + Login + ProductCard + ProductList + Cart + Checkout + OrderHistory + .env with auto-generated API key. The "do everything" route under the /epicmerch umbrella.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# EpicMerch Full Storefront Scaffold
|
|
7
|
+
|
|
8
|
+
You are integrating EpicMerch into this project. EpicMerch is a complete e-commerce backend that handles auth, inventory, orders, and payments. Follow every step below in order without asking for confirmation unless a step explicitly says to ask.
|
|
9
|
+
|
|
10
|
+
**Idempotency rule (apply throughout):** Before writing any file in the scaffold steps below, check if it already exists. If it does, SKIP that file and tell the user "X already exists — keeping it". Never overwrite existing files. The merchant may already have a polished version of any of these components; preserving their work is more important than completing the scaffold.
|
|
11
|
+
|
|
12
|
+
## Checklist
|
|
13
|
+
|
|
14
|
+
- [ ] **Step 0: Survey the project**
|
|
15
|
+
|
|
16
|
+
List which of the scaffold target files already exist in this project:
|
|
17
|
+
- `src/lib/epicmerch.js`
|
|
18
|
+
- `src/components/Login.jsx`
|
|
19
|
+
- `src/components/ProductCard.jsx`
|
|
20
|
+
- `src/components/ProductList.jsx`
|
|
21
|
+
- `src/components/Cart.jsx`
|
|
22
|
+
- `src/components/Checkout.jsx`
|
|
23
|
+
- `src/components/OrderHistory.jsx`
|
|
24
|
+
- `.env.example`
|
|
25
|
+
- `.env`
|
|
26
|
+
|
|
27
|
+
Briefly tell the user which already exist (those will be skipped) and which will be created. Then proceed.
|
|
28
|
+
|
|
29
|
+
- [ ] **Step 1: Detect framework**
|
|
30
|
+
|
|
31
|
+
Read `package.json`. Determine the framework:
|
|
32
|
+
- If `"react"` or `"vite"` in dependencies → framework = `react`, envPrefix = `VITE_`
|
|
33
|
+
- If `"next"` in dependencies → framework = `react`, envPrefix = `NEXT_PUBLIC_`
|
|
34
|
+
- If `"vue"` in dependencies → framework = `react`, envPrefix = `VITE_`; tell the user: "Vue is not yet natively supported — installing React templates instead."
|
|
35
|
+
- Otherwise → framework = `react`, envPrefix = `VITE_`
|
|
36
|
+
|
|
37
|
+
Tell the user: "Detected [framework]. Installing EpicMerch SDK..."
|
|
38
|
+
|
|
39
|
+
- [ ] **Step 2: Install the SDK**
|
|
40
|
+
|
|
41
|
+
Check `package.json` first. If `"epicmerch"` is already in `dependencies`, skip the install and tell the user "epicmerch SDK already installed". Otherwise:
|
|
42
|
+
|
|
43
|
+
Run: `npm install epicmerch`
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 3: Create SDK initializer**
|
|
46
|
+
|
|
47
|
+
**If `src/lib/epicmerch.js` already exists, skip this step entirely** and tell the user "src/lib/epicmerch.js already exists — keeping it". Otherwise, write `src/lib/epicmerch.js` using the `envPrefix` detected in Step 1 to substitute the env var names:
|
|
48
|
+
|
|
49
|
+
- For `VITE_` prefix (Vite / React):
|
|
50
|
+
```js
|
|
51
|
+
import EpicMerch from 'epicmerch';
|
|
52
|
+
|
|
53
|
+
export const store = new EpicMerch({
|
|
54
|
+
apiKey: import.meta.env.VITE_API_KEY,
|
|
55
|
+
...(import.meta.env.VITE_API_URL && { baseUrl: import.meta.env.VITE_API_URL }),
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- For `NEXT_PUBLIC_` prefix (Next.js):
|
|
60
|
+
```js
|
|
61
|
+
import EpicMerch from 'epicmerch';
|
|
62
|
+
|
|
63
|
+
export const store = new EpicMerch({
|
|
64
|
+
apiKey: process.env.NEXT_PUBLIC_API_KEY,
|
|
65
|
+
...(process.env.NEXT_PUBLIC_API_URL && { baseUrl: process.env.NEXT_PUBLIC_API_URL }),
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- [ ] **Step 4: Scaffold auth**
|
|
70
|
+
|
|
71
|
+
**If `src/components/Login.jsx` already exists, skip this step** and tell the user "Login.jsx already exists — keeping it". Otherwise write `src/components/Login.jsx`:
|
|
72
|
+
|
|
73
|
+
```jsx
|
|
74
|
+
import { useState } from 'react';
|
|
75
|
+
import { store } from '../lib/epicmerch';
|
|
76
|
+
|
|
77
|
+
export default function Login({ onLogin }) {
|
|
78
|
+
const [phone, setPhone] = useState('');
|
|
79
|
+
const [otp, setOtp] = useState('');
|
|
80
|
+
const [step, setStep] = useState('phone');
|
|
81
|
+
|
|
82
|
+
const sendOtp = async () => {
|
|
83
|
+
await store.auth.sendOtp(phone, 'phone');
|
|
84
|
+
setStep('otp');
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const verify = async () => {
|
|
88
|
+
const result = await store.auth.verifyOtp(phone, otp, {});
|
|
89
|
+
if (result.token) {
|
|
90
|
+
store.setCustomerToken(result.token);
|
|
91
|
+
localStorage.setItem('customerInfo', JSON.stringify(result));
|
|
92
|
+
onLogin(result);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div>
|
|
98
|
+
{step === 'phone' ? (
|
|
99
|
+
<>
|
|
100
|
+
<input value={phone} onChange={e => setPhone(e.target.value)} placeholder="+919876543210" />
|
|
101
|
+
<button onClick={sendOtp}>Send OTP</button>
|
|
102
|
+
</>
|
|
103
|
+
) : (
|
|
104
|
+
<>
|
|
105
|
+
<input value={otp} onChange={e => setOtp(e.target.value)} placeholder="Enter OTP" />
|
|
106
|
+
<button onClick={verify}>Verify</button>
|
|
107
|
+
</>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- [ ] **Step 5: Scaffold product catalog**
|
|
115
|
+
|
|
116
|
+
For each of the two files in this step, check existence individually. If a file already exists, skip writing it and tell the user "X already exists — keeping it". Otherwise write it.
|
|
117
|
+
|
|
118
|
+
Write `src/components/ProductCard.jsx`:
|
|
119
|
+
|
|
120
|
+
```jsx
|
|
121
|
+
export default function ProductCard({ product, onAddToCart }) {
|
|
122
|
+
return (
|
|
123
|
+
<div className="product-card">
|
|
124
|
+
<img src={product.images?.[0]} alt={product.name} />
|
|
125
|
+
<h3>{product.name}</h3>
|
|
126
|
+
<p>₹{product.price}</p>
|
|
127
|
+
<button onClick={() => onAddToCart(product._id)}>Add to Cart</button>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Write `src/components/ProductList.jsx`:
|
|
134
|
+
|
|
135
|
+
```jsx
|
|
136
|
+
import { useEffect, useState } from 'react';
|
|
137
|
+
import { store } from '../lib/epicmerch';
|
|
138
|
+
import ProductCard from './ProductCard';
|
|
139
|
+
|
|
140
|
+
export default function ProductList() {
|
|
141
|
+
const [products, setProducts] = useState([]);
|
|
142
|
+
const [categories, setCategories] = useState([]);
|
|
143
|
+
const [category, setCategory] = useState(undefined);
|
|
144
|
+
const [query, setQuery] = useState('');
|
|
145
|
+
|
|
146
|
+
useEffect(() => { store.categories.list().then(setCategories); }, []);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (query.trim()) {
|
|
150
|
+
store.products.search(query).then(d => setProducts(d.products ?? d));
|
|
151
|
+
} else {
|
|
152
|
+
store.products.list({ type: category, limit: 12 }).then(d => setProducts(d.products ?? d));
|
|
153
|
+
}
|
|
154
|
+
}, [query, category]);
|
|
155
|
+
|
|
156
|
+
const addToCart = (productId) => store.cart.add(productId, 1);
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div>
|
|
160
|
+
<input
|
|
161
|
+
placeholder="Search products..."
|
|
162
|
+
value={query}
|
|
163
|
+
onChange={e => setQuery(e.target.value)}
|
|
164
|
+
/>
|
|
165
|
+
<div>
|
|
166
|
+
{categories.map(c => (
|
|
167
|
+
<button key={c} onClick={() => { setQuery(''); setCategory(c === 'All' ? undefined : c); }}>{c}</button>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
<div className="product-grid">
|
|
171
|
+
{products.map(p => <ProductCard key={p._id} product={p} onAddToCart={addToCart} />)}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- [ ] **Step 5b: Wire up an existing search bar (only if `ProductList.jsx` was skipped)**
|
|
179
|
+
|
|
180
|
+
If you wrote a fresh `ProductList.jsx` in Step 5, skip this step — the canonical scaffold already has search wired.
|
|
181
|
+
|
|
182
|
+
If `ProductList.jsx` was SKIPPED because it already existed, the merchant has their own UI. They may have a search bar somewhere — in a `Navbar`, a `Header`, a dedicated `SearchBar.jsx`, a search page, etc. — that isn't currently calling EpicMerch's search API. Detect and offer to wire it up.
|
|
183
|
+
|
|
184
|
+
**Detection.** Look through `src/` for files containing any of these signals (case-insensitive):
|
|
185
|
+
- `<input` whose `placeholder` mentions `search`
|
|
186
|
+
- A state variable named `query`, `search`, `searchTerm`, `searchQuery`, or `keyword`
|
|
187
|
+
- A handler named `onSearch`, `handleSearch`, `onQueryChange`, or similar
|
|
188
|
+
|
|
189
|
+
For each match, check whether the file already calls `store.products.search(`. If yes, it's wired — skip silently.
|
|
190
|
+
|
|
191
|
+
**For each unwired match:** show the merchant the file path and the snippet you found, then say:
|
|
192
|
+
|
|
193
|
+
> "I noticed a search input in `<path>` that doesn't call EpicMerch's search yet. The one-line wire-up is:
|
|
194
|
+
>
|
|
195
|
+
> ```js
|
|
196
|
+
> import { store } from '../lib/epicmerch'; // add at top if missing
|
|
197
|
+
>
|
|
198
|
+
> // inside the search handler / useEffect:
|
|
199
|
+
> store.products.search(query).then(d => setProducts(d.products ?? d));
|
|
200
|
+
> ```
|
|
201
|
+
>
|
|
202
|
+
> Want me to apply this to your component? (yes / no — I'll leave it alone if no.)"
|
|
203
|
+
|
|
204
|
+
**WAIT for an explicit yes before editing.** If the merchant declines, move on without changing the file.
|
|
205
|
+
|
|
206
|
+
If you find no search bar anywhere in `src/`, skip this step silently — don't tell the merchant "no search bar found", just continue to Step 6.
|
|
207
|
+
|
|
208
|
+
- [ ] **Step 6: Scaffold cart + orders**
|
|
209
|
+
|
|
210
|
+
For each of the three files in this step (Cart.jsx, Checkout.jsx, OrderHistory.jsx), check existence individually. If a file already exists, skip writing it and tell the user "X already exists — keeping it". Otherwise write it.
|
|
211
|
+
|
|
212
|
+
Write `src/components/Cart.jsx`:
|
|
213
|
+
|
|
214
|
+
```jsx
|
|
215
|
+
import { useEffect, useState } from 'react';
|
|
216
|
+
import { store } from '../lib/epicmerch';
|
|
217
|
+
|
|
218
|
+
export default function Cart({ onCheckout }) {
|
|
219
|
+
const [cart, setCart] = useState({ items: [] });
|
|
220
|
+
|
|
221
|
+
useEffect(() => { store.cart.get().then(setCart); }, []);
|
|
222
|
+
|
|
223
|
+
const remove = async (productId) => {
|
|
224
|
+
await store.cart.remove(productId);
|
|
225
|
+
store.cart.get().then(setCart);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const total = cart.items?.reduce((s, i) => s + i.price * i.qty, 0) ?? 0;
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div>
|
|
232
|
+
{cart.items?.map(item => (
|
|
233
|
+
<div key={item.productId}>
|
|
234
|
+
<span>{item.name} x{item.qty}</span>
|
|
235
|
+
<span>₹{item.price * item.qty}</span>
|
|
236
|
+
<button onClick={() => remove(item.productId)}>Remove</button>
|
|
237
|
+
</div>
|
|
238
|
+
))}
|
|
239
|
+
<p>Total: ₹{total}</p>
|
|
240
|
+
<button onClick={onCheckout}>Checkout</button>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Write `src/components/Checkout.jsx`:
|
|
247
|
+
|
|
248
|
+
```jsx
|
|
249
|
+
import { useState } from 'react';
|
|
250
|
+
import { store } from '../lib/epicmerch';
|
|
251
|
+
|
|
252
|
+
const loadRazorpay = () =>
|
|
253
|
+
new Promise((resolve) => {
|
|
254
|
+
if (window.Razorpay) return resolve(true);
|
|
255
|
+
const script = document.createElement('script');
|
|
256
|
+
script.src = 'https://checkout.razorpay.com/v1/checkout.js';
|
|
257
|
+
script.onload = () => resolve(true);
|
|
258
|
+
script.onerror = () => resolve(false);
|
|
259
|
+
document.body.appendChild(script);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
export default function Checkout({ cart, onSuccess }) {
|
|
263
|
+
const [address, setAddress] = useState({ street: '', city: '', postalCode: '', country: 'India' });
|
|
264
|
+
|
|
265
|
+
const placeOrder = async () => {
|
|
266
|
+
await loadRazorpay();
|
|
267
|
+
const total = cart.items.reduce((s, i) => s + i.price * i.qty, 0);
|
|
268
|
+
const { orderId } = await store.orders.create({
|
|
269
|
+
orderItems: cart.items.map(i => ({ productId: i.productId, qty: i.qty, price: i.price })),
|
|
270
|
+
shippingAddress: address,
|
|
271
|
+
paymentMethod: 'Razorpay',
|
|
272
|
+
totalPrice: total,
|
|
273
|
+
});
|
|
274
|
+
const config = await store.payment.getConfig();
|
|
275
|
+
const { razorpayOrderId } = await store.payment.createOrder(total * 100, orderId);
|
|
276
|
+
const rzp = new window.Razorpay({
|
|
277
|
+
key: config.razorpayKeyId,
|
|
278
|
+
order_id: razorpayOrderId,
|
|
279
|
+
amount: total * 100,
|
|
280
|
+
handler: async (response) => {
|
|
281
|
+
await store.payment.verify({ ...response, orderId });
|
|
282
|
+
await store.cart.clear();
|
|
283
|
+
onSuccess(orderId);
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
rzp.open();
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div>
|
|
291
|
+
<input placeholder="Street" onChange={e => setAddress(a => ({ ...a, street: e.target.value }))} />
|
|
292
|
+
<input placeholder="City" onChange={e => setAddress(a => ({ ...a, city: e.target.value }))} />
|
|
293
|
+
<input placeholder="Postal Code" onChange={e => setAddress(a => ({ ...a, postalCode: e.target.value }))} />
|
|
294
|
+
<button onClick={placeOrder}>Place Order</button>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Do NOT add any Razorpay script tag to `index.html` — the script loads dynamically only when the user clicks Place Order.
|
|
301
|
+
|
|
302
|
+
Write `src/components/OrderHistory.jsx`:
|
|
303
|
+
|
|
304
|
+
```jsx
|
|
305
|
+
import { useEffect, useState } from 'react';
|
|
306
|
+
import { store } from '../lib/epicmerch';
|
|
307
|
+
|
|
308
|
+
export default function OrderHistory() {
|
|
309
|
+
const [orders, setOrders] = useState([]);
|
|
310
|
+
|
|
311
|
+
useEffect(() => { store.orders.list().then(setOrders); }, []);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div>
|
|
315
|
+
<h2>Your Orders</h2>
|
|
316
|
+
{orders.map(order => (
|
|
317
|
+
<div key={order._id}>
|
|
318
|
+
<span>Order #{order._id.slice(-6)}</span>
|
|
319
|
+
<span> — ₹{order.totalPrice}</span>
|
|
320
|
+
<span> — {order.status}</span>
|
|
321
|
+
</div>
|
|
322
|
+
))}
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- [ ] **Step 7: Generate API key via MCP + write `.env`**
|
|
329
|
+
|
|
330
|
+
This step has two parts: write `.env.example` (the template), then generate a real API key and write a working `.env`.
|
|
331
|
+
|
|
332
|
+
**Part A — `.env.example`:** If `.env.example` already exists, skip writing it and tell the user ".env.example already exists — keeping it". Otherwise write `.env.example`:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
VITE_API_KEY=your_epicmerch_api_key_here
|
|
336
|
+
# VITE_API_URL is OPTIONAL — only set it for local development.
|
|
337
|
+
# Production: leave it unset; the SDK defaults to https://api.epicmerch.in/api
|
|
338
|
+
# Local dev: VITE_API_URL=http://localhost:5002/api
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Check `.gitignore` — if `.env` is not listed, append `.env` to `.gitignore` before any `.env` file is written.
|
|
342
|
+
|
|
343
|
+
**Part B — generate a real API key + write `.env`:**
|
|
344
|
+
|
|
345
|
+
First, read `.env` if it exists. If it already contains a `VITE_API_KEY=` line whose value is NOT the placeholder string `your_epicmerch_api_key_here` (i.e. there's already a real key), skip Part B entirely and tell the user "Existing API key in .env — keeping it".
|
|
346
|
+
|
|
347
|
+
Otherwise, the merchant is authenticated via the EpicMerch MCP (this skill assumes the MCP is connected — that's how it was invoked). Generate a key by calling the MCP tool:
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
merchant_generate_api_key({ name: "Storefront" })
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
The response will include the new key. Extract it (the field is typically `key` or `apiKey` — check the response shape). Then write/update `.env`.
|
|
354
|
+
|
|
355
|
+
**Production (default).** Write only the key — the SDK already knows the prod URL:
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
VITE_API_KEY=<the generated key from the MCP response>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Local development.** Only if the merchant is clearly running against a local EpicMerch server (e.g. they explicitly said so, or their existing `.env` already had a `localhost` URL, or the MCP was wired with `EPICMERCH_API_URL=http://localhost:...`), ALSO write the override:
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
VITE_API_KEY=<the generated key from the MCP response>
|
|
365
|
+
VITE_API_URL=http://localhost:5002/api
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
When in doubt, default to the production form (no `VITE_API_URL` line). Don't ask the merchant — guess from context.
|
|
369
|
+
|
|
370
|
+
**Fallback if MCP isn't available:** If the `merchant_generate_api_key` tool fails or isn't connected, tell the user: "MCP tool unavailable. Generate a key manually at https://epicmerch.in/dashboard → API Keys and add it to .env." Do NOT fail the whole flow — continue to Step 8.
|
|
371
|
+
|
|
372
|
+
- [ ] **Step 8: Tell the user what to do next**
|
|
373
|
+
|
|
374
|
+
Print a summary using the actual state of the project:
|
|
375
|
+
|
|
376
|
+
- List the files you CREATED in this run (skip the ones that were already present).
|
|
377
|
+
- If you skipped any, briefly mention them as "kept as-is".
|
|
378
|
+
- If Step 5b wired up a search bar in an existing component, mention which file you edited and that it now calls `store.products.search()`.
|
|
379
|
+
- If the API key was generated automatically, mention that ".env now has a working VITE_API_KEY — your storefront should load real product data on next refresh."
|
|
380
|
+
- Then output the next-steps block exactly:
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
Next steps:
|
|
384
|
+
1. (API key auto-generated and saved to .env — if not, get one at https://epicmerch.in/dashboard → API Keys)
|
|
385
|
+
2. Restart your dev server (npm run dev) to pick up the .env changes
|
|
386
|
+
3. Import the new components into your App.jsx where you need them
|
|
387
|
+
|
|
388
|
+
Need help with payments, analytics, or notifications? Ask me!
|
|
389
|
+
```
|