Add ZarinPal e-payment and related checkout/search fixes.

Business settings can store ZarinPal merchant credentials, and the payment registry can initiate and verify ZarinPal alongside Mellat.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-16 01:45:41 +03:30
co-authored by Cursor
parent c76c3967bb
commit b991a60cbc
19 changed files with 542 additions and 39 deletions
+3 -3
View File
@@ -349,9 +349,9 @@ Base: `/tenants/:host/user-products`
| 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=…` |
| POST/GET | `/payments/:gateway/callback` | Bank/gateway return URL (Mellat, ZarinPal). Verifies, 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`.
Gateway credentials live in `businesses.settings.store.ePayment` (dashboard: **Settings**). Adapter registry: **Mellat** + **ZarinPal**; stubs reserved for `sep`, `snappay`, `digipay`.
#### Orders
@@ -619,7 +619,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)
- Online e-payment (Mellat + ZarinPal; stubs for SEP / Snapp Pay / DigiPay)
- Product technical info
- Category variations & technical forms
- Tenant resolution by domain
+2 -2
View File
@@ -31,10 +31,10 @@ You are building a **Meshkee business website (storefront)**. You must use the M
### Typical bootstrap sequence
1. `GET /tenants/{domain}` → branding + `businessId`
2. Homepage: business-info, sliders, category-groups, brand-groups, store-specials
3. Catalog: categories, products, store-items, **user-products** (customer stock listings)
3. Catalog: categories, products, store-items (`name` instant search: in-stock first, then `updatedAt`), **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`
- 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
- Bank callback hits API then redirects to `returnUrl?status=success|failed&orderId=…`
- Enabled gateways: `GET /tenants/{domain}` → `ePayment`, or `GET /businesses/{businessId}/payments/methods`
@@ -1716,6 +1716,29 @@
"url": "{{baseUrl}}/businesses/{{businessId}}/payments/mellat/callback",
"description": "Called by Mellat (not by the website). Verifies+settles, then 303 redirect to returnUrl."
}
},
{
"name": "ZarinPal callback (gateway → API)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/businesses/{{businessId}}/payments/zarinpal/callback?Authority={{authority}}&Status=OK",
"host": ["{{baseUrl}}"],
"path": [
"businesses",
"{{businessId}}",
"payments",
"zarinpal",
"callback"
],
"query": [
{ "key": "Authority", "value": "{{authority}}" },
{ "key": "Status", "value": "OK" }
]
},
"description": "Called by ZarinPal (not by the website). Verifies payment, then 303 redirect to returnUrl."
}
}
]
},
+1
View File
@@ -645,6 +645,7 @@
{
"name": "name",
"in": "query",
"description": "Instant search on product title / nameFa. Results are ordered in-stock first (unlimited or qty > 0), then by updatedAt desc.",
"schema": {
"type": "string"
}