diff --git a/docs/website-api/AI_PROMPT.md b/docs/website-api/AI_PROMPT.md index 3531a54..669b49d 100644 --- a/docs/website-api/AI_PROMPT.md +++ b/docs/website-api/AI_PROMPT.md @@ -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/` → save `businessId` from `id`. 2. All public content uses `/tenants//...` (no auth). -3. Cart, orders, favorites use `/businesses//...` with `Authorization: Bearer `. -4. Customer register body must include `"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.`. 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": ""`. 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 `` / 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.` (see Shopping cart). Do not implement login or checkout here. +6. Bank payment callbacks stay on the store apex via nginx (`https:///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.`) + +The Meshkee **customer dashboard** lives at `https://customer.` (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./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.`). + +**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` | `.` (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 ` 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.` 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=.`, `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": "", + "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 ``; `encoded = encodeGuestCart(items)`): + +- Logged in (`meshkee_customer_access_token` present): + `https://customer./checkout/cart?guestCart=` +- Not logged in: + `https://customer./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.`. + +### Checkout & payments (customer dashboard — not this website) + +OpenAPI **Cart** / checkout / payment-method routes are for `https://customer.`, not storefront JavaScript. + +Nginx on the shop apex still proxies bank callbacks (`https:///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) diff --git a/docs/website-api/Meshkee-Website-API.postman_collection.json b/docs/website-api/Meshkee-Website-API.postman_collection.json index c153120..ae16f9a 100644 --- a/docs/website-api/Meshkee-Website-API.postman_collection.json +++ b/docs/website-api/Meshkee-Website-API.postman_collection.json @@ -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": [ diff --git a/docs/website-api/index.html b/docs/website-api/index.html index b62d41a..180b0a4 100644 --- a/docs/website-api/index.html +++ b/docs/website-api/index.html @@ -89,9 +89,48 @@
  • Variable domain = website apex only (no www/api/customer/business).
  • GET /tenants/{domain} → businessId.
  • Public pages: /tenants/{domain}/... (no auth) — products, user-products, blogs, portfolios, store-items, etc.
  • -
  • Cart / orders / favorites: /businesses/{businessId}/... + Bearer JWT.
  • +
  • Favorites (optional, if cookies exist): /businesses/{businessId}/... + Bearer JWT.
  • +
  • Login, checkout, server cart, orders: customer dashboard at https://customer.{domain} — not pages on the shop.
  • +

    Shared login with customer dashboard

    +

    + Do not build a login / register / OTP page on the storefront. + Send shoppers to https://customer.{domain}/login. + The customer dashboard writes parent-domain cookies; the shop only + reads them to detect an existing session: +

    +
      +
    • meshkee_customer_access_token
    • +
    • meshkee_customer_refresh_token
    • +
    +

    + Set Domain=.{domain}, Path=/, SameSite=Lax. + API auth is still Authorization: Bearer <accessToken> (cookies are not sent to the API). + Do not use /auth/handoff for shoppers (staff-only into the business dashboard). + Full detail: AI_PROMPT.md. +

    + +

    Shopping cart on the storefront

    +

    + The shop implements a mini-cart only: header icon, quantity badge, + popup, Continue. Persist a guest cart as meshkee-guest-cart + (localStorage + parent-domain cookie). Each line id is + storeItemVariantId. + Add-to-cart: GET /tenants/{domain}/store-items/by-product/{productId}, + 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: +

    +
      +
    • Logged in → https://customer.{domain}/checkout/cart?guestCart=…
    • +
    • Not logged in → https://customer.{domain}/login?redirect=/checkout/cart?guestCart=…
    • +
    +

    + Do not call /cart or /cart/checkout from the website, + and do not add shop /login or /checkout routes. + Encoding and payload: AI_PROMPT.md (Shopping cart). +

    +

    Branding (favicon + logos)

    GET /tenants/{domain}/website/favicon returns diff --git a/docs/website-api/openapi.json b/docs/website-api/openapi.json index 0d07f54..a502453 100644 --- a/docs/website-api/openapi.json +++ b/docs/website-api/openapi.json @@ -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 `. 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", diff --git a/src/website-docs/static/AI_PROMPT.md b/src/website-docs/static/AI_PROMPT.md index 3531a54..669b49d 100644 --- a/src/website-docs/static/AI_PROMPT.md +++ b/src/website-docs/static/AI_PROMPT.md @@ -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/` → save `businessId` from `id`. 2. All public content uses `/tenants//...` (no auth). -3. Cart, orders, favorites use `/businesses//...` with `Authorization: Bearer `. -4. Customer register body must include `"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.`. 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": ""`. 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 `` / 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.` (see Shopping cart). Do not implement login or checkout here. +6. Bank payment callbacks stay on the store apex via nginx (`https:///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.`) + +The Meshkee **customer dashboard** lives at `https://customer.` (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./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.`). + +**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` | `.` (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 ` 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.` 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=.`, `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": "", + "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 ``; `encoded = encodeGuestCart(items)`): + +- Logged in (`meshkee_customer_access_token` present): + `https://customer./checkout/cart?guestCart=` +- Not logged in: + `https://customer./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.`. + +### Checkout & payments (customer dashboard — not this website) + +OpenAPI **Cart** / checkout / payment-method routes are for `https://customer.`, not storefront JavaScript. + +Nginx on the shop apex still proxies bank callbacks (`https:///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) diff --git a/src/website-docs/static/Meshkee-Website-API.postman_collection.json b/src/website-docs/static/Meshkee-Website-API.postman_collection.json index c153120..ae16f9a 100644 --- a/src/website-docs/static/Meshkee-Website-API.postman_collection.json +++ b/src/website-docs/static/Meshkee-Website-API.postman_collection.json @@ -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": [ diff --git a/src/website-docs/static/index.html b/src/website-docs/static/index.html index b62d41a..180b0a4 100644 --- a/src/website-docs/static/index.html +++ b/src/website-docs/static/index.html @@ -89,9 +89,48 @@

  • Variable domain = website apex only (no www/api/customer/business).
  • GET /tenants/{domain} → businessId.
  • Public pages: /tenants/{domain}/... (no auth) — products, user-products, blogs, portfolios, store-items, etc.
  • -
  • Cart / orders / favorites: /businesses/{businessId}/... + Bearer JWT.
  • +
  • Favorites (optional, if cookies exist): /businesses/{businessId}/... + Bearer JWT.
  • +
  • Login, checkout, server cart, orders: customer dashboard at https://customer.{domain} — not pages on the shop.
  • +

    Shared login with customer dashboard

    +

    + Do not build a login / register / OTP page on the storefront. + Send shoppers to https://customer.{domain}/login. + The customer dashboard writes parent-domain cookies; the shop only + reads them to detect an existing session: +

    +
      +
    • meshkee_customer_access_token
    • +
    • meshkee_customer_refresh_token
    • +
    +

    + Set Domain=.{domain}, Path=/, SameSite=Lax. + API auth is still Authorization: Bearer <accessToken> (cookies are not sent to the API). + Do not use /auth/handoff for shoppers (staff-only into the business dashboard). + Full detail: AI_PROMPT.md. +

    + +

    Shopping cart on the storefront

    +

    + The shop implements a mini-cart only: header icon, quantity badge, + popup, Continue. Persist a guest cart as meshkee-guest-cart + (localStorage + parent-domain cookie). Each line id is + storeItemVariantId. + Add-to-cart: GET /tenants/{domain}/store-items/by-product/{productId}, + 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: +

    +
      +
    • Logged in → https://customer.{domain}/checkout/cart?guestCart=…
    • +
    • Not logged in → https://customer.{domain}/login?redirect=/checkout/cart?guestCart=…
    • +
    +

    + Do not call /cart or /cart/checkout from the website, + and do not add shop /login or /checkout routes. + Encoding and payload: AI_PROMPT.md (Shopping cart). +

    +

    Branding (favicon + logos)

    GET /tenants/{domain}/website/favicon returns diff --git a/src/website-docs/static/openapi.json b/src/website-docs/static/openapi.json index 0d07f54..a502453 100644 --- a/src/website-docs/static/openapi.json +++ b/src/website-docs/static/openapi.json @@ -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 `. 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",