Document storefront mini-cart and customer-dashboard checkout.
Website agents must add variants to a local guest cart and redirect to customer.{domain} for login and checkout instead of building those pages on the shop.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
5c701d0841
commit
cf459807d8
+118
-14
@@ -22,26 +22,23 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
### Hard rules
|
||||
1. Resolve tenant first: `GET /tenants/<WEBSITE_DOMAIN>` → save `businessId` from `id`.
|
||||
2. All public content uses `/tenants/<WEBSITE_DOMAIN>/...` (no auth).
|
||||
3. Cart, orders, favorites use `/businesses/<businessId>/...` with `Authorization: Bearer <accessToken>`.
|
||||
4. Customer register body must include `"domain": "<WEBSITE_DOMAIN>"`. If the cell already exists on another Meshkee site and the password differs, API returns `409` with `CELL_EXISTS_OTHER_SITE:...`. Retry register with `"acknowledgeExistingAccount": true` to link that account (profile unchanged; **password is replaced** with the new signup password), then complete SMS OTP.
|
||||
5. Cell numbers are E.164 (`+98912...`).
|
||||
6. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
|
||||
7. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||
8. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||
9. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||
3. **Auth + checkout are not this website.** Login, register, OTP, shopping-cart process, addresses, and payment already exist for every Meshkee site at `https://customer.<WEBSITE_DOMAIN>`. Do **not** add `/login`, `/register`, `/checkout`, or `/cart` routes (or equivalent pages) on the storefront. Do **not** call `/businesses/{businessId}/cart` or `/cart/checkout` from this site.
|
||||
4. **Detect login** via parent-domain cookies (see Shared login). Not token handoff, not an API SSO endpoint. If the shopper needs to sign in, **redirect** to the customer dashboard login — do not invent a login UI.
|
||||
5. Storefront cart = header icon + quantity badge + mini-cart popup + **Continue** redirect (see Shopping cart). Server cart / orders / payment APIs are customer-dashboard only. Favorites may still use Bearer if cookies exist.
|
||||
6. Customer-dashboard auth APIs (storefronts must **not** call these): register body includes `"domain": "<WEBSITE_DOMAIN>"`. If the cell already exists on another Meshkee site and the password differs, API returns `409` with `CELL_EXISTS_OTHER_SITE:...`. Retry register with `"acknowledgeExistingAccount": true` to link that account (profile unchanged; **password is replaced** with the new signup password), then complete SMS OTP.
|
||||
7. Cell numbers are E.164 (`+98912...`).
|
||||
8. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
|
||||
9. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||
10. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||
11. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||
|
||||
### Typical bootstrap sequence
|
||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||
2. `GET /tenants/{domain}/website/favicon` → `faviconUrl` for `<link rel="icon">` / Next.js `metadata.icons` (falls back to `logoUrl` when no dedicated favicon). Also returns `logoUrl`, `logoDarkUrl`, `hasDedicatedFavicon`.
|
||||
3. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`)
|
||||
4. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc); each portfolio may include nullable `projectUrl` (external website link). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
|
||||
5. Auth: register/login → store tokens. Optional: `POST /auth/send-otp` then `POST /auth/login-otp` (passwordless) or `POST /auth/reset-password` (forgot password). `POST /auth/verify-otp` only marks the cell verified (no tokens).
|
||||
6. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
|
||||
- For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"` or `"zarinpal"`), and absolute `returnUrl`
|
||||
- Response includes `payment.redirect` `{ method, url, fields }` — POST/redirect shopper to the bank
|
||||
- Meshkee registers the bank `callback_url` on the **store apex** (`https://YOUR_WEBSITE_DOMAIN/meshkee/payments/{gateway}/callback`), which nginx proxies to the API. ZarinPal/Mellat domain checks must match the store domain, not `api.meshkee.com`.
|
||||
- After verify, API redirects the browser to `returnUrl?status=success|failed&orderId=…`
|
||||
- Enabled gateways: `GET /tenants/{domain}` → `ePayment`, or `GET /businesses/{businessId}/payments/methods`
|
||||
5. Shopping cart on **this** site only: header icon + badge + mini-cart popup. Persist a **guest cart** and **Continue** to `https://customer.<WEBSITE_DOMAIN>` (see Shopping cart). Do not implement login or checkout here.
|
||||
6. Bank payment callbacks stay on the store apex via nginx (`https://<WEBSITE_DOMAIN>/meshkee/payments/{gateway}/callback`) — infrastructure only. The website app does **not** implement payment or checkout pages; that UI is the customer dashboard.
|
||||
|
||||
### Favicon + logos
|
||||
- `GET /tenants/{domain}/website/favicon` → `{ faviconUrl, logoUrl, logoDarkUrl, hasDedicatedFavicon }`
|
||||
@@ -49,6 +46,113 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
- When `hasDedicatedFavicon` is false, `faviconUrl` equals the light `logoUrl` — still safe to use as tab icon.
|
||||
- Header/footer logos: `logoUrl` on light backgrounds, `logoDarkUrl` on dark (fallback to `logoUrl` in CSS when null).
|
||||
|
||||
### Shared login with customer dashboard (`customer.<WEBSITE_DOMAIN>`)
|
||||
|
||||
The Meshkee **customer dashboard** lives at `https://customer.<WEBSITE_DOMAIN>` (e.g. `customer.sgemed.com`). **That** app owns login, register, OTP, and password reset for every storefront. The shop and the dashboard stay in sync via **parent-domain cookies** — not a login page on `{apex}`.
|
||||
|
||||
**Storefront requirements:**
|
||||
- Do **not** build login / register / OTP / forgot-password UI.
|
||||
- If the shopper must authenticate, send them to `https://customer.<WEBSITE_DOMAIN>/login?redirect=…` (relative redirect path only, e.g. `/checkout/cart`).
|
||||
- On page load: if the access-token cookie exists, treat them as logged in (Continue can skip login).
|
||||
- Do not invent OAuth/SSO APIs. Do not use `POST /auth/handoff` (staff-only into `business.<WEBSITE_DOMAIN>`).
|
||||
|
||||
**Cookie contract** (written by the **customer dashboard** on login; storefronts only **read** them):
|
||||
|
||||
| Cookie name | Value |
|
||||
|-------------|--------|
|
||||
| `meshkee_customer_access_token` | access JWT (URL-encoded; may be chunked as `name_n` + `name_0`…) |
|
||||
| `meshkee_customer_refresh_token` | refresh JWT (same) |
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|--------|
|
||||
| `Domain` | `.<WEBSITE_DOMAIN>` (leading dot), e.g. `.sgemed.com` |
|
||||
| `Path` | `/` |
|
||||
| `SameSite` | `Lax` |
|
||||
| `Max-Age` | ~30 days (cleared on logout) |
|
||||
| `Secure` | set on HTTPS |
|
||||
|
||||
API calls (favorites, etc.) still send `Authorization: Bearer <accessToken>` from that cookie. Cookies are **not** sent to the API for auth.
|
||||
|
||||
**Wrong:** a custom login page on the storefront, or calling `/auth/login` from shop UI.
|
||||
**Right:** redirect to `customer.<WEBSITE_DOMAIN>` and read apex cookies.
|
||||
|
||||
### Shopping cart on the storefront
|
||||
|
||||
Website scope is a **mini-cart only**. The full cart, checkout, addresses, and payment run on the customer dashboard.
|
||||
|
||||
**UI**
|
||||
- Header shopping-cart icon.
|
||||
- Badge = sum of line quantities (hide or `0` when empty).
|
||||
- Click → popup: lines, qty, remove, total, **Continue**.
|
||||
|
||||
**Add to cart** (always a **variant**, never a product id)
|
||||
|
||||
1. `GET /tenants/{domain}/store-items/by-product/{productId}` → `{ storeItem: { variants: [...] } | null }`.
|
||||
2. If `storeItem` is null or `variants` is empty → hide Add to cart (not for sale).
|
||||
3. **In-stock only:** `stockQuantity === null` (unlimited) or `stockQuantity > 0`. Disable / omit the rest.
|
||||
4. **If more than one in-stock variant:** list them first (`label` is already joined, e.g. `قرمز · XL` from `selections`). The shopper **must pick one** — do not add until they choose.
|
||||
5. **If exactly one in-stock variant:** add that variant (no picker required).
|
||||
6. Push that variant into the guest cart. `id` = variant `id` (`storeItemVariantId`). If that `id` is already in the cart, **increment `quantity`** instead of adding a second line.
|
||||
7. Selling price: `discountedPrice ?? price`. Optional `originalPrice` = `price` when `discountedPrice` is set.
|
||||
|
||||
Do not call `POST /businesses/{id}/cart/items` from the storefront. Mini-cart is local only.
|
||||
|
||||
**Guest cart** (must match the customer dashboard):
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Storage key / cookie name | `meshkee-guest-cart` |
|
||||
| Persist | `localStorage` **and** cookie `Domain=.<WEBSITE_DOMAIN>`, `Path=/`, `SameSite=Lax`, ~30 days |
|
||||
| Cookie size | if `encodeURIComponent(json)` is longer than ~3500 chars, skip the cookie and rely on the URL param |
|
||||
|
||||
Each line:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "<storeItemVariantId>",
|
||||
"name": "...",
|
||||
"slug": "...",
|
||||
"price": 123000,
|
||||
"originalPrice": 150000,
|
||||
"image": "...",
|
||||
"quantity": 1
|
||||
}
|
||||
```
|
||||
|
||||
`id` **must** be `storeItemVariantId` (not product id, not store-item id). `price` is IRT, numeric.
|
||||
|
||||
**URL payload** (always pass on Continue — cookies can be dropped or too large):
|
||||
|
||||
```js
|
||||
function encodeGuestCart(items) {
|
||||
const json = JSON.stringify(items)
|
||||
return btoa(unescape(encodeURIComponent(json)))
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '')
|
||||
}
|
||||
```
|
||||
|
||||
Query/hash param name: `guestCart`.
|
||||
|
||||
**Continue** (replace `<WEBSITE_DOMAIN>`; `encoded = encodeGuestCart(items)`):
|
||||
|
||||
- Logged in (`meshkee_customer_access_token` present):
|
||||
`https://customer.<WEBSITE_DOMAIN>/checkout/cart?guestCart=<encoded>`
|
||||
- Not logged in:
|
||||
`https://customer.<WEBSITE_DOMAIN>/login?redirect=${encodeURIComponent('/checkout/cart?guestCart=' + encoded)}`
|
||||
|
||||
The dashboard reads `guestCart` (query or hash), then `localStorage`, then the shared cookie, and syncs lines into the server cart after login.
|
||||
|
||||
**Wrong:** storefront `/cart` or `/checkout` pages; `POST /businesses/{id}/cart/checkout`; a shop-built login.
|
||||
**Right:** local guest mini-cart → redirect to `customer.<WEBSITE_DOMAIN>`.
|
||||
|
||||
### Checkout & payments (customer dashboard — not this website)
|
||||
|
||||
OpenAPI **Cart** / checkout / payment-method routes are for `https://customer.<WEBSITE_DOMAIN>`, not storefront JavaScript.
|
||||
|
||||
Nginx on the shop apex still proxies bank callbacks (`https://<WEBSITE_DOMAIN>/meshkee/payments/{gateway}/callback`) to the API. Do not add a Next.js page for that path. After pay, the API redirects to the dashboard `returnUrl` (`/checkout/result?status=…`).
|
||||
|
||||
### User products (customer listings)
|
||||
Public marketplace listings owned by customers — not catalog `products`.
|
||||
- `GET /tenants/{domain}/user-products` — list published (`name`/`q`, `categoryId`, `cityId`, `countryId`, `condition`, `promoted`, pagination)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "Meshkee Website API (Global)",
|
||||
"description": "# Meshkee Website API — Global reference for all storefronts\n\nCanonical docs: https://api.meshkee.com/docs/website\n\nThis collection is **not** tied to one business. Every Meshkee website (any domain) uses the same endpoints.\n\n## How multi-tenancy works\n1. Set collection variable `domain` = the **website apex** only (e.g. `example.com`, `sanihome.ir`). Never use `www.` / `api.` / `customer.` / `business.` here.\n2. Set `baseUrl` (see below).\n3. Run **Resolve tenant** → saves `businessId`.\n4. Public content: `/tenants/{{domain}}/...` (no auth).\n5. After login: cart / orders / favorites use `/businesses/{{businessId}}/...` with Bearer token.\n\n## baseUrl options (same Nest API)\n- Preferred central: `https://api.meshkee.com/api/v1`\n- Per-site alias (if DNS+SSL configured): `https://api.{{domain}}/api/v1`\n- Local: `http://localhost:3000/api/v1`\n\nTenant is always taken from the **path** (`/tenants/{domain}`), not from the API hostname.\n\n## Auth\n- Public: no header\n- Customer: `Authorization: Bearer {{accessToken}}`\n- Register requires body field `domain` = same website apex\n\n## Not in this collection\nCMS / dashboard / super-admin APIs (staff only).\n",
|
||||
"description": "# Meshkee Website API — Global reference for all storefronts\n\nCanonical docs: https://api.meshkee.com/docs/website\n\nThis collection is **not** tied to one business. Every Meshkee website (any domain) uses the same endpoints.\n\n## How multi-tenancy works\n1. Set collection variable `domain` = the **website apex** only (e.g. `example.com`, `sanihome.ir`). Never use `www.` / `api.` / `customer.` / `business.` here.\n2. Set `baseUrl` (see below).\n3. Run **Resolve tenant** → saves `businessId`.\n4. Public content: `/tenants/{{domain}}/...` (no auth).\n5. **Do not** build login or checkout on the storefront. Auth, server cart, addresses, and payment live on `https://customer.{{domain}}`. The shop mini-cart is a local guest cart (`meshkee-guest-cart`); add-to-cart loads `GET /tenants/{{domain}}/store-items/by-product/{productId}`, lists variants if there is more than one, then adds the chosen `storeItemVariantId`. Continue redirects to the customer dashboard (see AI_PROMPT.md).\n6. Favorites (optional) may use `/businesses/{{businessId}}/...` with Bearer if the shopper already has dashboard cookies.\n\n## baseUrl options (same Nest API)\n- Preferred central: `https://api.meshkee.com/api/v1`\n- Per-site alias (if DNS+SSL configured): `https://api.{{domain}}/api/v1`\n- Local: `http://localhost:3000/api/v1`\n\nTenant is always taken from the **path** (`/tenants/{domain}`), not from the API hostname.\n\n## Auth\n- Public: no header\n- Customer dashboard: `Authorization: Bearer {{accessToken}}`\n- Storefronts must **not** call register/login — redirect to `https://customer.{{domain}}/login`\n- The dashboard writes parent-domain cookies `meshkee_customer_access_token` and `meshkee_customer_refresh_token` on `Domain=.{{domain}}` (`Path=/`, `SameSite=Lax`). The shop only **reads** them. Do **not** use `/auth/handoff` for shoppers (staff SSO into the business dashboard).\n\n## Not in this collection\nCMS / dashboard / super-admin APIs (staff only).\n",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"variable": [
|
||||
|
||||
@@ -89,9 +89,48 @@
|
||||
<li>Variable <code>domain</code> = website apex only (no <code>www</code>/<code>api</code>/<code>customer</code>/<code>business</code>).</li>
|
||||
<li><code>GET /tenants/{domain}</code> → <code>businessId</code>.</li>
|
||||
<li>Public pages: <code>/tenants/{domain}/...</code> (no auth) — products, <strong>user-products</strong>, blogs, portfolios, store-items, etc.</li>
|
||||
<li>Cart / orders / favorites: <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
|
||||
<li>Favorites (optional, if cookies exist): <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
|
||||
<li>Login, checkout, server cart, orders: <strong>customer dashboard</strong> at <code>https://customer.{domain}</code> — not pages on the shop.</li>
|
||||
</ol>
|
||||
|
||||
<h2>Shared login with customer dashboard</h2>
|
||||
<p>
|
||||
Do <strong>not</strong> build a login / register / OTP page on the storefront.
|
||||
Send shoppers to <code>https://customer.{domain}/login</code>.
|
||||
The customer dashboard writes parent-domain cookies; the shop only
|
||||
<strong>reads</strong> them to detect an existing session:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>meshkee_customer_access_token</code></li>
|
||||
<li><code>meshkee_customer_refresh_token</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
Set <code>Domain=.{domain}</code>, <code>Path=/</code>, <code>SameSite=Lax</code>.
|
||||
API auth is still <code>Authorization: Bearer <accessToken></code> (cookies are not sent to the API).
|
||||
Do not use <code>/auth/handoff</code> for shoppers (staff-only into the business dashboard).
|
||||
Full detail: <a href="/docs/website/AI_PROMPT.md">AI_PROMPT.md</a>.
|
||||
</p>
|
||||
|
||||
<h2>Shopping cart on the storefront</h2>
|
||||
<p>
|
||||
The shop implements a <strong>mini-cart only</strong>: header icon, quantity badge,
|
||||
popup, Continue. Persist a guest cart as <code>meshkee-guest-cart</code>
|
||||
(localStorage + parent-domain cookie). Each line <code>id</code> is
|
||||
<code>storeItemVariantId</code>.
|
||||
Add-to-cart: <code>GET /tenants/{domain}/store-items/by-product/{productId}</code>,
|
||||
list variants if there is more than one, then add the chosen variant
|
||||
(same id already in cart → increment quantity). Continue goes to the customer dashboard:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Logged in → <code>https://customer.{domain}/checkout/cart?guestCart=…</code></li>
|
||||
<li>Not logged in → <code>https://customer.{domain}/login?redirect=/checkout/cart?guestCart=…</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
Do not call <code>/cart</code> or <code>/cart/checkout</code> from the website,
|
||||
and do not add shop <code>/login</code> or <code>/checkout</code> routes.
|
||||
Encoding and payload: <a href="/docs/website/AI_PROMPT.md">AI_PROMPT.md</a> (Shopping cart).
|
||||
</p>
|
||||
|
||||
<h2>Branding (favicon + logos)</h2>
|
||||
<p>
|
||||
<code>GET /tenants/{domain}/website/favicon</code> returns
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"info": {
|
||||
"title": "Meshkee Website API",
|
||||
"version": "1.0.0",
|
||||
"description": "Global storefront API for every Meshkee business website.\n\n**Not domain-specific.** Replace `{domain}` with the website apex (e.g. `sanihome.ir`).\n\n**Base URL:** `https://api.meshkee.com/api/v1` (or `https://api.{domain}/api/v1` if that alias is configured).\n\n**Tenant rule:** public content uses `/tenants/{domain}/...`. After login, cart/orders/favorites use `/businesses/{businessId}/...` with Bearer JWT.\n\n**Technical specs (AI agents):** Detail endpoints may return `technicalValues` with **values only** (`fieldId` + text/option ids — **no labels**). To render label→value technical details, always call the matching `.../technical-info` endpoint and join `form.fields[].id` to `values[].fieldId`. There is no public `product-category-variation-fields` route.\n\n**Docs:** https://api.meshkee.com/docs/website"
|
||||
"description": "Global storefront API for every Meshkee business website.\n\n**Not domain-specific.** Replace `{domain}` with the website apex (e.g. `sanihome.ir`).\n\n**Base URL:** `https://api.meshkee.com/api/v1` (or `https://api.{domain}/api/v1` if that alias is configured).\n\n**Tenant rule:** public content uses `/tenants/{domain}/...`.\n\n**Storefront vs customer dashboard:** Do **not** build login, register, OTP, checkout, or cart pages on the shop. Those live at `https://customer.{domain}` for every Meshkee site. The website mini-cart is a **local guest cart** (`meshkee-guest-cart`); Continue redirects to the dashboard. Load variants with `GET /tenants/{domain}/store-items/by-product/{productId}` — if more than one in-stock variant, list them and let the shopper pick one before add. See AI_PROMPT.md.\n\n**Shared login:** the customer dashboard writes parent-domain cookies `meshkee_customer_access_token` / `meshkee_customer_refresh_token` on `Domain=.{domain}`. The shop only **reads** them (to skip login on Continue). API calls still use Bearer. Not `POST /auth/handoff` (staff SSO into the business dashboard).\n\n**Technical specs (AI agents):** Detail endpoints may return `technicalValues` with **values only** (`fieldId` + text/option ids — **no labels**). To render label→value technical details, always call the matching `.../technical-info` endpoint and join `form.fields[].id` to `values[].fieldId`. There is no public `product-category-variation-fields` route.\n\n**Docs:** https://api.meshkee.com/docs/website"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@@ -64,7 +64,8 @@
|
||||
"name": "Contact"
|
||||
},
|
||||
{
|
||||
"name": "Auth"
|
||||
"name": "Auth",
|
||||
"description": "Used by the **customer dashboard** (`https://customer.{domain}`), not storefront UI. Do not build login/register/OTP pages on the shop — redirect to `https://customer.{domain}/login`.\n\nAPI calls use `Authorization: Bearer <accessToken>`. The dashboard writes parent-domain cookies `meshkee_customer_access_token` / `meshkee_customer_refresh_token` on `Domain=.{domain}`; the shop only reads them. Do not use `/auth/handoff` for shoppers (staff-only into the business dashboard)."
|
||||
},
|
||||
{
|
||||
"name": "Addresses"
|
||||
@@ -73,7 +74,8 @@
|
||||
"name": "Cities"
|
||||
},
|
||||
{
|
||||
"name": "Cart"
|
||||
"name": "Cart",
|
||||
"description": "Server cart + checkout — **customer dashboard only**. Storefronts must not call these routes. The shop keeps a local guest mini-cart (`meshkee-guest-cart`); add-to-cart uses `storeItemVariantId` from `GET /tenants/{domain}/store-items/by-product/{productId}` (list variants if more than one, then add the chosen line)."
|
||||
},
|
||||
{
|
||||
"name": "Orders"
|
||||
@@ -86,7 +88,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Payments",
|
||||
"description": "Online e-payment gateways (website checkout)"
|
||||
"description": "Online e-payment gateways — customer dashboard checkout, not storefront pages. Bank callbacks are nginx on the shop apex (`/meshkee/payments/{gateway}/callback`)."
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
@@ -94,7 +96,8 @@
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
"bearerFormat": "JWT",
|
||||
"description": "Customer access JWT from customer-dashboard login/register/OTP/refresh. Storefronts do not obtain this via a shop login page — they read `meshkee_customer_access_token` if the dashboard already set it on `Domain=.{domain}`. Always send Bearer on API requests; cookies are not sent to the API."
|
||||
},
|
||||
"apiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
|
||||
@@ -22,26 +22,23 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
### Hard rules
|
||||
1. Resolve tenant first: `GET /tenants/<WEBSITE_DOMAIN>` → save `businessId` from `id`.
|
||||
2. All public content uses `/tenants/<WEBSITE_DOMAIN>/...` (no auth).
|
||||
3. Cart, orders, favorites use `/businesses/<businessId>/...` with `Authorization: Bearer <accessToken>`.
|
||||
4. Customer register body must include `"domain": "<WEBSITE_DOMAIN>"`. If the cell already exists on another Meshkee site and the password differs, API returns `409` with `CELL_EXISTS_OTHER_SITE:...`. Retry register with `"acknowledgeExistingAccount": true` to link that account (profile unchanged; **password is replaced** with the new signup password), then complete SMS OTP.
|
||||
5. Cell numbers are E.164 (`+98912...`).
|
||||
6. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
|
||||
7. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||
8. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||
9. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||
3. **Auth + checkout are not this website.** Login, register, OTP, shopping-cart process, addresses, and payment already exist for every Meshkee site at `https://customer.<WEBSITE_DOMAIN>`. Do **not** add `/login`, `/register`, `/checkout`, or `/cart` routes (or equivalent pages) on the storefront. Do **not** call `/businesses/{businessId}/cart` or `/cart/checkout` from this site.
|
||||
4. **Detect login** via parent-domain cookies (see Shared login). Not token handoff, not an API SSO endpoint. If the shopper needs to sign in, **redirect** to the customer dashboard login — do not invent a login UI.
|
||||
5. Storefront cart = header icon + quantity badge + mini-cart popup + **Continue** redirect (see Shopping cart). Server cart / orders / payment APIs are customer-dashboard only. Favorites may still use Bearer if cookies exist.
|
||||
6. Customer-dashboard auth APIs (storefronts must **not** call these): register body includes `"domain": "<WEBSITE_DOMAIN>"`. If the cell already exists on another Meshkee site and the password differs, API returns `409` with `CELL_EXISTS_OTHER_SITE:...`. Retry register with `"acknowledgeExistingAccount": true` to link that account (profile unchanged; **password is replaced** with the new signup password), then complete SMS OTP.
|
||||
7. Cell numbers are E.164 (`+98912...`).
|
||||
8. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
|
||||
9. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||
10. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||
11. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||
|
||||
### Typical bootstrap sequence
|
||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||
2. `GET /tenants/{domain}/website/favicon` → `faviconUrl` for `<link rel="icon">` / Next.js `metadata.icons` (falls back to `logoUrl` when no dedicated favicon). Also returns `logoUrl`, `logoDarkUrl`, `hasDedicatedFavicon`.
|
||||
3. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`)
|
||||
4. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc); each portfolio may include nullable `projectUrl` (external website link). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
|
||||
5. Auth: register/login → store tokens. Optional: `POST /auth/send-otp` then `POST /auth/login-otp` (passwordless) or `POST /auth/reset-password` (forgot password). `POST /auth/verify-otp` only marks the cell verified (no tokens).
|
||||
6. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
|
||||
- For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"` or `"zarinpal"`), and absolute `returnUrl`
|
||||
- Response includes `payment.redirect` `{ method, url, fields }` — POST/redirect shopper to the bank
|
||||
- Meshkee registers the bank `callback_url` on the **store apex** (`https://YOUR_WEBSITE_DOMAIN/meshkee/payments/{gateway}/callback`), which nginx proxies to the API. ZarinPal/Mellat domain checks must match the store domain, not `api.meshkee.com`.
|
||||
- After verify, API redirects the browser to `returnUrl?status=success|failed&orderId=…`
|
||||
- Enabled gateways: `GET /tenants/{domain}` → `ePayment`, or `GET /businesses/{businessId}/payments/methods`
|
||||
5. Shopping cart on **this** site only: header icon + badge + mini-cart popup. Persist a **guest cart** and **Continue** to `https://customer.<WEBSITE_DOMAIN>` (see Shopping cart). Do not implement login or checkout here.
|
||||
6. Bank payment callbacks stay on the store apex via nginx (`https://<WEBSITE_DOMAIN>/meshkee/payments/{gateway}/callback`) — infrastructure only. The website app does **not** implement payment or checkout pages; that UI is the customer dashboard.
|
||||
|
||||
### Favicon + logos
|
||||
- `GET /tenants/{domain}/website/favicon` → `{ faviconUrl, logoUrl, logoDarkUrl, hasDedicatedFavicon }`
|
||||
@@ -49,6 +46,113 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
- When `hasDedicatedFavicon` is false, `faviconUrl` equals the light `logoUrl` — still safe to use as tab icon.
|
||||
- Header/footer logos: `logoUrl` on light backgrounds, `logoDarkUrl` on dark (fallback to `logoUrl` in CSS when null).
|
||||
|
||||
### Shared login with customer dashboard (`customer.<WEBSITE_DOMAIN>`)
|
||||
|
||||
The Meshkee **customer dashboard** lives at `https://customer.<WEBSITE_DOMAIN>` (e.g. `customer.sgemed.com`). **That** app owns login, register, OTP, and password reset for every storefront. The shop and the dashboard stay in sync via **parent-domain cookies** — not a login page on `{apex}`.
|
||||
|
||||
**Storefront requirements:**
|
||||
- Do **not** build login / register / OTP / forgot-password UI.
|
||||
- If the shopper must authenticate, send them to `https://customer.<WEBSITE_DOMAIN>/login?redirect=…` (relative redirect path only, e.g. `/checkout/cart`).
|
||||
- On page load: if the access-token cookie exists, treat them as logged in (Continue can skip login).
|
||||
- Do not invent OAuth/SSO APIs. Do not use `POST /auth/handoff` (staff-only into `business.<WEBSITE_DOMAIN>`).
|
||||
|
||||
**Cookie contract** (written by the **customer dashboard** on login; storefronts only **read** them):
|
||||
|
||||
| Cookie name | Value |
|
||||
|-------------|--------|
|
||||
| `meshkee_customer_access_token` | access JWT (URL-encoded; may be chunked as `name_n` + `name_0`…) |
|
||||
| `meshkee_customer_refresh_token` | refresh JWT (same) |
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|--------|
|
||||
| `Domain` | `.<WEBSITE_DOMAIN>` (leading dot), e.g. `.sgemed.com` |
|
||||
| `Path` | `/` |
|
||||
| `SameSite` | `Lax` |
|
||||
| `Max-Age` | ~30 days (cleared on logout) |
|
||||
| `Secure` | set on HTTPS |
|
||||
|
||||
API calls (favorites, etc.) still send `Authorization: Bearer <accessToken>` from that cookie. Cookies are **not** sent to the API for auth.
|
||||
|
||||
**Wrong:** a custom login page on the storefront, or calling `/auth/login` from shop UI.
|
||||
**Right:** redirect to `customer.<WEBSITE_DOMAIN>` and read apex cookies.
|
||||
|
||||
### Shopping cart on the storefront
|
||||
|
||||
Website scope is a **mini-cart only**. The full cart, checkout, addresses, and payment run on the customer dashboard.
|
||||
|
||||
**UI**
|
||||
- Header shopping-cart icon.
|
||||
- Badge = sum of line quantities (hide or `0` when empty).
|
||||
- Click → popup: lines, qty, remove, total, **Continue**.
|
||||
|
||||
**Add to cart** (always a **variant**, never a product id)
|
||||
|
||||
1. `GET /tenants/{domain}/store-items/by-product/{productId}` → `{ storeItem: { variants: [...] } | null }`.
|
||||
2. If `storeItem` is null or `variants` is empty → hide Add to cart (not for sale).
|
||||
3. **In-stock only:** `stockQuantity === null` (unlimited) or `stockQuantity > 0`. Disable / omit the rest.
|
||||
4. **If more than one in-stock variant:** list them first (`label` is already joined, e.g. `قرمز · XL` from `selections`). The shopper **must pick one** — do not add until they choose.
|
||||
5. **If exactly one in-stock variant:** add that variant (no picker required).
|
||||
6. Push that variant into the guest cart. `id` = variant `id` (`storeItemVariantId`). If that `id` is already in the cart, **increment `quantity`** instead of adding a second line.
|
||||
7. Selling price: `discountedPrice ?? price`. Optional `originalPrice` = `price` when `discountedPrice` is set.
|
||||
|
||||
Do not call `POST /businesses/{id}/cart/items` from the storefront. Mini-cart is local only.
|
||||
|
||||
**Guest cart** (must match the customer dashboard):
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| Storage key / cookie name | `meshkee-guest-cart` |
|
||||
| Persist | `localStorage` **and** cookie `Domain=.<WEBSITE_DOMAIN>`, `Path=/`, `SameSite=Lax`, ~30 days |
|
||||
| Cookie size | if `encodeURIComponent(json)` is longer than ~3500 chars, skip the cookie and rely on the URL param |
|
||||
|
||||
Each line:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "<storeItemVariantId>",
|
||||
"name": "...",
|
||||
"slug": "...",
|
||||
"price": 123000,
|
||||
"originalPrice": 150000,
|
||||
"image": "...",
|
||||
"quantity": 1
|
||||
}
|
||||
```
|
||||
|
||||
`id` **must** be `storeItemVariantId` (not product id, not store-item id). `price` is IRT, numeric.
|
||||
|
||||
**URL payload** (always pass on Continue — cookies can be dropped or too large):
|
||||
|
||||
```js
|
||||
function encodeGuestCart(items) {
|
||||
const json = JSON.stringify(items)
|
||||
return btoa(unescape(encodeURIComponent(json)))
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '')
|
||||
}
|
||||
```
|
||||
|
||||
Query/hash param name: `guestCart`.
|
||||
|
||||
**Continue** (replace `<WEBSITE_DOMAIN>`; `encoded = encodeGuestCart(items)`):
|
||||
|
||||
- Logged in (`meshkee_customer_access_token` present):
|
||||
`https://customer.<WEBSITE_DOMAIN>/checkout/cart?guestCart=<encoded>`
|
||||
- Not logged in:
|
||||
`https://customer.<WEBSITE_DOMAIN>/login?redirect=${encodeURIComponent('/checkout/cart?guestCart=' + encoded)}`
|
||||
|
||||
The dashboard reads `guestCart` (query or hash), then `localStorage`, then the shared cookie, and syncs lines into the server cart after login.
|
||||
|
||||
**Wrong:** storefront `/cart` or `/checkout` pages; `POST /businesses/{id}/cart/checkout`; a shop-built login.
|
||||
**Right:** local guest mini-cart → redirect to `customer.<WEBSITE_DOMAIN>`.
|
||||
|
||||
### Checkout & payments (customer dashboard — not this website)
|
||||
|
||||
OpenAPI **Cart** / checkout / payment-method routes are for `https://customer.<WEBSITE_DOMAIN>`, not storefront JavaScript.
|
||||
|
||||
Nginx on the shop apex still proxies bank callbacks (`https://<WEBSITE_DOMAIN>/meshkee/payments/{gateway}/callback`) to the API. Do not add a Next.js page for that path. After pay, the API redirects to the dashboard `returnUrl` (`/checkout/result?status=…`).
|
||||
|
||||
### User products (customer listings)
|
||||
Public marketplace listings owned by customers — not catalog `products`.
|
||||
- `GET /tenants/{domain}/user-products` — list published (`name`/`q`, `categoryId`, `cityId`, `countryId`, `condition`, `promoted`, pagination)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "Meshkee Website API (Global)",
|
||||
"description": "# Meshkee Website API — Global reference for all storefronts\n\nCanonical docs: https://api.meshkee.com/docs/website\n\nThis collection is **not** tied to one business. Every Meshkee website (any domain) uses the same endpoints.\n\n## How multi-tenancy works\n1. Set collection variable `domain` = the **website apex** only (e.g. `example.com`, `sanihome.ir`). Never use `www.` / `api.` / `customer.` / `business.` here.\n2. Set `baseUrl` (see below).\n3. Run **Resolve tenant** → saves `businessId`.\n4. Public content: `/tenants/{{domain}}/...` (no auth).\n5. After login: cart / orders / favorites use `/businesses/{{businessId}}/...` with Bearer token.\n\n## baseUrl options (same Nest API)\n- Preferred central: `https://api.meshkee.com/api/v1`\n- Per-site alias (if DNS+SSL configured): `https://api.{{domain}}/api/v1`\n- Local: `http://localhost:3000/api/v1`\n\nTenant is always taken from the **path** (`/tenants/{domain}`), not from the API hostname.\n\n## Auth\n- Public: no header\n- Customer: `Authorization: Bearer {{accessToken}}`\n- Register requires body field `domain` = same website apex\n\n## Not in this collection\nCMS / dashboard / super-admin APIs (staff only).\n",
|
||||
"description": "# Meshkee Website API — Global reference for all storefronts\n\nCanonical docs: https://api.meshkee.com/docs/website\n\nThis collection is **not** tied to one business. Every Meshkee website (any domain) uses the same endpoints.\n\n## How multi-tenancy works\n1. Set collection variable `domain` = the **website apex** only (e.g. `example.com`, `sanihome.ir`). Never use `www.` / `api.` / `customer.` / `business.` here.\n2. Set `baseUrl` (see below).\n3. Run **Resolve tenant** → saves `businessId`.\n4. Public content: `/tenants/{{domain}}/...` (no auth).\n5. **Do not** build login or checkout on the storefront. Auth, server cart, addresses, and payment live on `https://customer.{{domain}}`. The shop mini-cart is a local guest cart (`meshkee-guest-cart`); add-to-cart loads `GET /tenants/{{domain}}/store-items/by-product/{productId}`, lists variants if there is more than one, then adds the chosen `storeItemVariantId`. Continue redirects to the customer dashboard (see AI_PROMPT.md).\n6. Favorites (optional) may use `/businesses/{{businessId}}/...` with Bearer if the shopper already has dashboard cookies.\n\n## baseUrl options (same Nest API)\n- Preferred central: `https://api.meshkee.com/api/v1`\n- Per-site alias (if DNS+SSL configured): `https://api.{{domain}}/api/v1`\n- Local: `http://localhost:3000/api/v1`\n\nTenant is always taken from the **path** (`/tenants/{domain}`), not from the API hostname.\n\n## Auth\n- Public: no header\n- Customer dashboard: `Authorization: Bearer {{accessToken}}`\n- Storefronts must **not** call register/login — redirect to `https://customer.{{domain}}/login`\n- The dashboard writes parent-domain cookies `meshkee_customer_access_token` and `meshkee_customer_refresh_token` on `Domain=.{{domain}}` (`Path=/`, `SameSite=Lax`). The shop only **reads** them. Do **not** use `/auth/handoff` for shoppers (staff SSO into the business dashboard).\n\n## Not in this collection\nCMS / dashboard / super-admin APIs (staff only).\n",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"variable": [
|
||||
|
||||
@@ -89,9 +89,48 @@
|
||||
<li>Variable <code>domain</code> = website apex only (no <code>www</code>/<code>api</code>/<code>customer</code>/<code>business</code>).</li>
|
||||
<li><code>GET /tenants/{domain}</code> → <code>businessId</code>.</li>
|
||||
<li>Public pages: <code>/tenants/{domain}/...</code> (no auth) — products, <strong>user-products</strong>, blogs, portfolios, store-items, etc.</li>
|
||||
<li>Cart / orders / favorites: <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
|
||||
<li>Favorites (optional, if cookies exist): <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
|
||||
<li>Login, checkout, server cart, orders: <strong>customer dashboard</strong> at <code>https://customer.{domain}</code> — not pages on the shop.</li>
|
||||
</ol>
|
||||
|
||||
<h2>Shared login with customer dashboard</h2>
|
||||
<p>
|
||||
Do <strong>not</strong> build a login / register / OTP page on the storefront.
|
||||
Send shoppers to <code>https://customer.{domain}/login</code>.
|
||||
The customer dashboard writes parent-domain cookies; the shop only
|
||||
<strong>reads</strong> them to detect an existing session:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>meshkee_customer_access_token</code></li>
|
||||
<li><code>meshkee_customer_refresh_token</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
Set <code>Domain=.{domain}</code>, <code>Path=/</code>, <code>SameSite=Lax</code>.
|
||||
API auth is still <code>Authorization: Bearer <accessToken></code> (cookies are not sent to the API).
|
||||
Do not use <code>/auth/handoff</code> for shoppers (staff-only into the business dashboard).
|
||||
Full detail: <a href="/docs/website/AI_PROMPT.md">AI_PROMPT.md</a>.
|
||||
</p>
|
||||
|
||||
<h2>Shopping cart on the storefront</h2>
|
||||
<p>
|
||||
The shop implements a <strong>mini-cart only</strong>: header icon, quantity badge,
|
||||
popup, Continue. Persist a guest cart as <code>meshkee-guest-cart</code>
|
||||
(localStorage + parent-domain cookie). Each line <code>id</code> is
|
||||
<code>storeItemVariantId</code>.
|
||||
Add-to-cart: <code>GET /tenants/{domain}/store-items/by-product/{productId}</code>,
|
||||
list variants if there is more than one, then add the chosen variant
|
||||
(same id already in cart → increment quantity). Continue goes to the customer dashboard:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Logged in → <code>https://customer.{domain}/checkout/cart?guestCart=…</code></li>
|
||||
<li>Not logged in → <code>https://customer.{domain}/login?redirect=/checkout/cart?guestCart=…</code></li>
|
||||
</ul>
|
||||
<p>
|
||||
Do not call <code>/cart</code> or <code>/cart/checkout</code> from the website,
|
||||
and do not add shop <code>/login</code> or <code>/checkout</code> routes.
|
||||
Encoding and payload: <a href="/docs/website/AI_PROMPT.md">AI_PROMPT.md</a> (Shopping cart).
|
||||
</p>
|
||||
|
||||
<h2>Branding (favicon + logos)</h2>
|
||||
<p>
|
||||
<code>GET /tenants/{domain}/website/favicon</code> returns
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"info": {
|
||||
"title": "Meshkee Website API",
|
||||
"version": "1.0.0",
|
||||
"description": "Global storefront API for every Meshkee business website.\n\n**Not domain-specific.** Replace `{domain}` with the website apex (e.g. `sanihome.ir`).\n\n**Base URL:** `https://api.meshkee.com/api/v1` (or `https://api.{domain}/api/v1` if that alias is configured).\n\n**Tenant rule:** public content uses `/tenants/{domain}/...`. After login, cart/orders/favorites use `/businesses/{businessId}/...` with Bearer JWT.\n\n**Technical specs (AI agents):** Detail endpoints may return `technicalValues` with **values only** (`fieldId` + text/option ids — **no labels**). To render label→value technical details, always call the matching `.../technical-info` endpoint and join `form.fields[].id` to `values[].fieldId`. There is no public `product-category-variation-fields` route.\n\n**Docs:** https://api.meshkee.com/docs/website"
|
||||
"description": "Global storefront API for every Meshkee business website.\n\n**Not domain-specific.** Replace `{domain}` with the website apex (e.g. `sanihome.ir`).\n\n**Base URL:** `https://api.meshkee.com/api/v1` (or `https://api.{domain}/api/v1` if that alias is configured).\n\n**Tenant rule:** public content uses `/tenants/{domain}/...`.\n\n**Storefront vs customer dashboard:** Do **not** build login, register, OTP, checkout, or cart pages on the shop. Those live at `https://customer.{domain}` for every Meshkee site. The website mini-cart is a **local guest cart** (`meshkee-guest-cart`); Continue redirects to the dashboard. Load variants with `GET /tenants/{domain}/store-items/by-product/{productId}` — if more than one in-stock variant, list them and let the shopper pick one before add. See AI_PROMPT.md.\n\n**Shared login:** the customer dashboard writes parent-domain cookies `meshkee_customer_access_token` / `meshkee_customer_refresh_token` on `Domain=.{domain}`. The shop only **reads** them (to skip login on Continue). API calls still use Bearer. Not `POST /auth/handoff` (staff SSO into the business dashboard).\n\n**Technical specs (AI agents):** Detail endpoints may return `technicalValues` with **values only** (`fieldId` + text/option ids — **no labels**). To render label→value technical details, always call the matching `.../technical-info` endpoint and join `form.fields[].id` to `values[].fieldId`. There is no public `product-category-variation-fields` route.\n\n**Docs:** https://api.meshkee.com/docs/website"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@@ -64,7 +64,8 @@
|
||||
"name": "Contact"
|
||||
},
|
||||
{
|
||||
"name": "Auth"
|
||||
"name": "Auth",
|
||||
"description": "Used by the **customer dashboard** (`https://customer.{domain}`), not storefront UI. Do not build login/register/OTP pages on the shop — redirect to `https://customer.{domain}/login`.\n\nAPI calls use `Authorization: Bearer <accessToken>`. The dashboard writes parent-domain cookies `meshkee_customer_access_token` / `meshkee_customer_refresh_token` on `Domain=.{domain}`; the shop only reads them. Do not use `/auth/handoff` for shoppers (staff-only into the business dashboard)."
|
||||
},
|
||||
{
|
||||
"name": "Addresses"
|
||||
@@ -73,7 +74,8 @@
|
||||
"name": "Cities"
|
||||
},
|
||||
{
|
||||
"name": "Cart"
|
||||
"name": "Cart",
|
||||
"description": "Server cart + checkout — **customer dashboard only**. Storefronts must not call these routes. The shop keeps a local guest mini-cart (`meshkee-guest-cart`); add-to-cart uses `storeItemVariantId` from `GET /tenants/{domain}/store-items/by-product/{productId}` (list variants if more than one, then add the chosen line)."
|
||||
},
|
||||
{
|
||||
"name": "Orders"
|
||||
@@ -86,7 +88,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Payments",
|
||||
"description": "Online e-payment gateways (website checkout)"
|
||||
"description": "Online e-payment gateways — customer dashboard checkout, not storefront pages. Bank callbacks are nginx on the shop apex (`/meshkee/payments/{gateway}/callback`)."
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
@@ -94,7 +96,8 @@
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
"bearerFormat": "JWT",
|
||||
"description": "Customer access JWT from customer-dashboard login/register/OTP/refresh. Storefronts do not obtain this via a shop login page — they read `meshkee_customer_access_token` if the dashboard already set it on `Domain=.{domain}`. Always send Bearer on API requests; cookies are not sent to the API."
|
||||
},
|
||||
"apiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
|
||||
Reference in New Issue
Block a user