Add Mellat e-payment, videos/instructions CMS, and legacy domain lookup.

Store gateway credentials per business, initiate/verify Mellat at checkout, and resolve old CMS ids from domains in the migrate modal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-13 09:51:17 +03:30
co-authored by Cursor
parent 513673a7da
commit d5e502cf77
44 changed files with 9303 additions and 996 deletions
+12 -2
View File
@@ -342,7 +342,16 @@ Base: `/tenants/:host/user-products`
| PATCH | `/cart/items/:itemId` | Update cart item quantity |
| DELETE | `/cart/items/:itemId` | Remove cart item |
| DELETE | `/cart` | Clear cart |
| POST | `/cart/checkout` | Place order from cart (requires `addressId` or `shippingAddress`) |
| POST | `/cart/checkout` | Place order from cart (`addressId` or `shippingAddress` + `payment`). For `e_payment_gate`, also send absolute `returnUrl`; response includes `payment.redirect` for bank redirect |
#### Payments (public bank callbacks + methods)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/payments/methods` | Enabled gateways for this business (no secrets). Also on `GET /tenants/:host` as `ePayment` |
| POST/GET | `/payments/:gateway/callback` | Bank return URL (Mellat first). Verifies/settles, then 303 → `returnUrl?status=…` |
Gateway credentials live in `businesses.settings.store.ePayment` (dashboard: Website → E-Payment). Adapter registry supports Mellat now; stubs reserved for `sep`, `snappay`, `digipay`, `zarinpal`.
#### Orders
@@ -584,7 +593,7 @@ See `.env.example` for the full list. Key groups:
|-------|-----------|
| Database | `DATABASE_URL`, `POSTGRES_*` |
| Redis | `REDIS_URL`, `REDIS_HOST`, `REDIS_PORT` |
| API | `PORT` |
| API | `PORT`, `API_PUBLIC_BASE_URL` (bank payment callbacks) |
| JWT | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `JWT_*_EXPIRES_IN` |
| SMS | `SMS_ENABLED`, `SMS_GAMA_BASE_URL`, `SMS_GAMA_USERNAME`, `SMS_GAMA_PASSWORD`, `SMS_GAMA_SOURCE_SERVICE`, `SMS_PARTNERS` |
| S3 | `S3_ENDPOINT`, `S3_BUCKET`, `S3_PUBLIC_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` |
@@ -608,6 +617,7 @@ See `.env.example` for the full list. Key groups:
- Product variation values (which options a product offers)
- Store items / product variants (price, stock, SKU)
- Shopping cart + checkout + orders (customer + admin)
- Online e-payment (Mellat first; multi-gateway registry for SEP / Snapp Pay / DigiPay / Zarinpal)
- Product technical info
- Category variations & technical forms
- Tenant resolution by domain
+4
View File
@@ -34,6 +34,10 @@ You are building a **Meshkee business website (storefront)**. You must use the M
3. Catalog: categories, products, store-items, **user-products** (customer stock listings)
4. 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).
5. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
- For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"`), and absolute `returnUrl`
- Response includes `payment.redirect` `{ method, url, fields }` — POST/redirect shopper to the bank
- Bank callback hits API then redirects to `returnUrl?status=success|failed&orderId=…`
- Enabled gateways: `GET /tenants/{domain}` → `ePayment`, or `GET /businesses/{businessId}/payments/methods`
### User products (customer listings)
Public marketplace listings owned by customers — not catalog `products`.
@@ -767,6 +767,113 @@
}
]
},
{
"name": "Videos",
"item": [
{
"name": "List published videos (website)",
"event": [
{
"listen": "test",
"script": {
"exec": [
"if (pm.response.code === 200) {",
" const json = pm.response.json();",
" if (json.items?.[0]?.id) pm.collectionVariables.set('videoId', json.items[0].id);",
" if (json.items?.[0]?.slug) pm.collectionVariables.set('videoSlug', json.items[0].slug);",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/tenants/{{domain}}/videos?page=1&pageSize=12",
"host": [
"{{baseUrl}}"
],
"path": [
"tenants",
"{{domain}}",
"videos"
],
"query": [
{
"key": "page",
"value": "1"
},
{
"key": "pageSize",
"value": "12"
},
{
"key": "provider",
"value": "",
"disabled": true
},
{
"key": "categoryId",
"value": "",
"disabled": true
},
{
"key": "title",
"value": "",
"disabled": true
}
]
}
}
},
{
"name": "Get published video by slug (website)",
"request": {
"method": "GET",
"url": "{{baseUrl}}/tenants/{{domain}}/videos/{{videoSlug}}"
}
},
{
"name": "List video comments (website)",
"request": {
"method": "GET",
"url": "{{baseUrl}}/tenants/{{domain}}/videos/{{videoId}}/comments"
}
},
{
"name": "Submit video comment (website)",
"event": [
{
"listen": "test",
"script": {
"exec": [
"if (pm.response.code === 200 || pm.response.code === 201) {",
" const json = pm.response.json();",
" if (json.comment?.id) pm.collectionVariables.set('commentId', json.comment.id);",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"authorName\": \"Video Viewer\",\n \"authorEmail\": \"viewer@example.com\",\n \"text\": \"Great video!\"\n}"
},
"url": "{{baseUrl}}/tenants/{{domain}}/videos/{{videoId}}/comments"
}
}
]
},
{
"name": "Portfolios",
"item": [
@@ -1526,7 +1633,7 @@
}
},
{
"name": "Checkout cart (saved address)",
"name": "Checkout cart (e-payment / Mellat)",
"event": [
{
"listen": "test",
@@ -1555,9 +1662,59 @@
],
"body": {
"mode": "raw",
"raw": "{\n \"addressId\": \"{{addressId}}\",\n \"customerNotes\": \"Deliver after 5pm\",\n \"payment\": {\n \"type\": \"e_payment_gate\",\n \"gatewayType\": \"zarinpal\"\n }\n}"
"raw": "{\n \"addressId\": \"{{addressId}}\",\n \"customerNotes\": \"Deliver after 5pm\",\n \"returnUrl\": \"https://YOUR_WEBSITE_DOMAIN/checkout/result\",\n \"payment\": {\n \"type\": \"e_payment_gate\",\n \"gatewayType\": \"mellat\"\n }\n}"
},
"url": "{{baseUrl}}/businesses/{{businessId}}/cart/checkout"
"url": "{{baseUrl}}/businesses/{{businessId}}/cart/checkout",
"description": "Creates a pending order + transaction, calls the gateway, and returns payment.redirect { method, url, fields }. Website must POST/redirect the shopper to the bank. After payment, bank hits /payments/{gateway}/callback which redirects to returnUrl?status=success|failed."
}
}
]
},
{
"name": "Payments",
"item": [
{
"name": "List payment methods",
"request": {
"method": "GET",
"header": [],
"url": "{{baseUrl}}/businesses/{{businessId}}/payments/methods",
"description": "Public. Returns enabled gateways (no secrets). Also available on GET /tenants/{host} as ePayment."
}
},
{
"name": "Mellat callback (bank → API)",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/x-www-form-urlencoded"
}
],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "ResCode",
"value": "0"
},
{
"key": "SaleOrderId",
"value": "{{transactionId}}"
},
{
"key": "SaleReferenceId",
"value": "123456789"
},
{
"key": "RefId",
"value": "{{refId}}"
}
]
},
"url": "{{baseUrl}}/businesses/{{businessId}}/payments/mellat/callback",
"description": "Called by Mellat (not by the website). Verifies+settles, then 303 redirect to returnUrl."
}
}
]
File diff suppressed because it is too large Load Diff