API باز فروشیکس (نسخه 1)
ping امکانهای اختیاریتان را اعلام کنید، با ابزار سنجش امتحان کنید و نشانی و کلید را به فروشنده بدهید تا در «فروشگاهها ← سایت دیگر (API)» وارد کند.
افزونه وردپرس فروشیکس یک پیادهسازی کامل همین استاندارد است و نمونه خوبی برای خواندن است.
فهرست
۱. نشانی پایه و کلید اتصال
- نشانی پایه API: هر نشانی HTTPS دلخواه روی سایت شما، مثلاً
https://mysite.ir/api/forushix. هر عملیات یک مسیر زیر آن است:GET {پایه}/products،POST {پایه}/pricesو… - کلید اتصال: یک رشته تصادفی دستکم ۳۲ نویسه که سایت شما برای هر فروشگاه میسازد، نگه میدارد و به فروشنده نشان میدهد (با امکان «ساخت کلید تازه»). کلید هرگز در درخواست فرستاده نمیشود؛ فقط با آن امضا ساخته میشود.
- پاسخها نباید به نشانی دیگری منتقل (redirect) شوند و نباید کش شوند.
۲. امضای درخواست
هر درخواست این سرآیندها را دارد:
| سرآیند | مقدار |
|---|---|
X-FY-Timestamp | زمان یونیکس به ثانیه |
X-FY-Nonce | ۳۲ نویسه هگز تصادفی |
X-FY-Signature | HMAC-SHA256 هگز (حروف کوچک) پیام زیر با کلید اتصال |
X-FY-Actor | (اختیاری) نام کاربر پنل، URL-encode شده؛ برای ثبت در تاریخچه سایت شما |
پیام امضا شش خط است که با \n به هم وصل میشوند:
signature = hex( HMAC_SHA256( key,
METHOD + "\n" + ROUTE + "\n" + QUERY + "\n" + TIMESTAMP + "\n" + hex(SHA256(BODY)) + "\n" + NONCE ) )
GET products?page=2 → "GET\n/forushix/v1/remote/products\npage=2\n1767000000\ne3b0c442…b855\n9f86d081884c7d659a2feaa0c55ad015"
| بخش | مقدار |
|---|---|
METHOD | GET یا POST با حروف بزرگ |
ROUTE | همیشه /forushix/v1/remote/ و نام عملیات (مستقل از نشانی واقعی سایت شما) |
QUERY | پارامترهای کوئری، مرتبشده بر اساس نام، با کدگذاری RFC 3986 (مثل id=12&page=2)؛ بیپارامتر: رشته خالی |
TIMESTAMP | همان X-FY-Timestamp |
SHA256(BODY) | هش هگز بدنه خام درخواست؛ برای GET هش رشته خالی |
NONCE | همان X-FY-Nonce |
سایت شما باید درخواست را رد کند (کد ۴۰۱) اگر: امضا نبود یا درست نبود، اختلاف زمان بیش از 300 ثانیه بود، یا همین امضا قبلاً یک بار پذیرفته شده بود (امضاهای دیدهشده را دستکم ۱۰ دقیقه نگه دارید). مقایسه امضا را با تابع زمانثابت انجام دهید.
نمونه سنجش در PHP
function fy_verify(string $secret, string $method, string $action, array $query, string $body, array $h): bool {
$ts = $h['x-fy-timestamp'] ?? ''; $nonce = strtolower($h['x-fy-nonce'] ?? ''); $sig = strtolower($h['x-fy-signature'] ?? '');
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300 || !preg_match('/^[a-f0-9]{32}$/', $nonce)) return false;
ksort($query);
$msg = strtoupper($method) . "\n" . '/forushix/v1/remote/' . $action . "\n" . http_build_query($query, '', '&', PHP_QUERY_RFC3986)
. "\n" . $ts . "\n" . hash('sha256', $body) . "\n" . $nonce;
if (!hash_equals(hash_hmac('sha256', $msg, $secret), $sig)) return false;
return remember_once($sig, 600); // false اگر این امضا قبلاً دیده شده باشد
}
نمونه سنجش در Node.js
const crypto = require('crypto');
function fyVerify(secret, method, action, query, rawBody, h) {
const ts = h['x-fy-timestamp'] || '', nonce = (h['x-fy-nonce'] || '').toLowerCase(), sig = (h['x-fy-signature'] || '').toLowerCase();
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300 || !/^[a-f0-9]{32}$/.test(nonce)) return false;
const q = Object.keys(query).sort().map(k => encodeURIComponent(k) + '=' + encodeURIComponent(query[k])).join('&');
const msg = [method.toUpperCase(), '/forushix/v1/remote/' + action, q, ts, crypto.createHash('sha256').update(rawBody || '').digest('hex'), nonce].join('\n');
const want = crypto.createHmac('sha256', secret).update(msg).digest('hex');
return sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want)) && rememberOnce(sig, 600);
}
۳. قالب پاسخ و خطا
- همه پاسخها JSON با UTF-8 و کد ۲۰۰؛ متن خطا فارسی و قابل نمایش به فروشنده.
- خطای قابل انتظار (مثلاً سفارش پیدا نشد، قیمت نامعتبر):
{"ok": false, "error": "سفارش پیدا نشد."} - خطای امضا: کد ۴۰۱ با
{"code": "fy_bad_signature" | "fy_unsigned" | "fy_expired" | "fy_replay", "message": "..."} - عملیاتی که پیاده نکردهاید: کد ۴۰۴ با
{"code": "not_supported"}
۴. قواعد داده
- شناسهها عدد صحیحاند و شناسه تنوع (variation) نباید با شناسه هیچ محصولی یکی باشد.
- قیمتها رشته عددی به واحد پول فروشگاهاند (
"250000")؛regularقیمت اصلی،saleقیمت حراج یا""،priceقیمت فعلی،onSaleبولی. - موجودی:
stockیکی ازinstock،outofstock،onbackorder؛manageیعنی تعداد شمرده میشود وqtyعدد آن است (وگرنهnull). - وضعیت سفارش: یکی از
pending، processing، on-hold، completed، cancelled، refunded، failed(همان معنی ووکامرس؛ «on-hold» یعنی در انتظار بررسی یا تأیید پرداخت). - تاریخها ISO 8601 با منطقه زمانی (
2026-10-04T14:30:00+03:30). تاریخهای ورودی فیلترY-m-dبه وقت فروشگاهاند. - واحد پول در
pingاعلام میشود:تومانیاریال.
۵. هسته (لازم)
| عملیات | کار |
|---|---|
GET ping | مشخصات فروشگاه و فهرست امکانها |
GET products | همه محصولات |
GET categories | دستهها |
GET variations | تنوعهای یک محصول (?id=) |
POST prices | ذخیره قیمتها |
POST stock | ذخیره موجودی |
GET orders | فهرست سفارشها |
GET order | جزئیات یک سفارش (?id=) |
POST order-status | تغییر وضعیت سفارش |
GET ping
{"ok": true, "site": "فروشگاه نمونه", "url": "https://mysite.ir", "currency": "تومان", "lowStock": 5,
"version": "1.0.0", "api": "1",
"features": ["stats", "tracking", "notes", "customers", "reports", "events"]}
features فهرست امکانهای اختیاریای است که پیاده کردهاید (بخش ۶). پنل فقط همانها را به فروشنده نشان میدهد.
GET products
{"ok": true, "data": [
{"id": 12, "name": "شال نخی", "sku": "SH-1", "type": "simple", "status": "publish", "link": "https://mysite.ir/p/12",
"image": "https://mysite.ir/img/12-thumb.jpg", "brand": "", "brandId": 0, "cats": ["شال"], "catIds": [3],
"stock": "instock", "qty": 4, "manage": true, "lowAt": null,
"regular": "250000", "sale": "199000", "price": "199000", "onSale": true},
{"id": 13, "name": "مانتو", "type": "variable", "status": "publish", "stock": "instock", "qty": null, "manage": false,
"regular": "", "sale": "", "price": "900000", "onSale": false, "cats": [], "catIds": []}
]}
status: publish، draft یا private. برای محصول چندحالتی قیمت و موجودی در تنوعهاست.
GET categories
{"ok": true, "data": [{"id": 3, "name": "شال", "parent": 0}, {"id": 4, "name": "شال تابستانی", "parent": 3}]}
GET variations?id=13
{"ok": true, "data": [
{"id": 1301, "name": "مشکی / M", "sku": "", "stock": "instock", "qty": 3, "manage": true, "manageParent": false,
"regular": "900000", "sale": "", "price": "900000", "onSale": false}
]}
POST prices و POST stock
بدنه فهرست تغییرهاست (حداکثر ۱۰۰۰)؛ هر فیلدی که نیامده دست نمیخورد. parentId برای تنوع شناسه محصول مادر است (برای محصول ساده ۰).
POST prices {"changes": [{"id": 12, "parentId": 0, "regular": "260000", "sale": ""},
{"id": 1301, "parentId": 13, "sale": "850000"}]}
POST stock {"changes": [{"id": 12, "manage": true, "qty": 7},
{"id": 14, "manage": false, "stock": "outofstock"}]}
پاسخ هر دو: {"ok": true, "updated": [12], "failed": [{"id": 1301, "message": "قیمت حراج باید کمتر از قیمت اصلی باشد."}]}
ردیف نامعتبر یا ناموجود به failed میرود و بقیه ذخیره میشوند. قیمت حراج باید کمتر از قیمت اصلی باشد و قیمت منفی پذیرفته نیست.
GET orders
پارامترها (همه اختیاری): status، search (نام، موبایل یا شماره سفارش)، page، per_page (۵ تا ۵۰)، from و to (Y-m-d)، customer. مرتب از تازه به قدیم.
{"ok": true, "page": 1, "totalPages": 8, "total": 152, "data": [
{"id": 1002, "number": "1002", "status": "processing", "total": 450000, "date": "2026-10-04T14:30:00+03:30",
"name": "سارا محمدی", "tracking": null, "source": "", "customer": 0,
"phone": "09121112233", "address": "تهران، تهران، ونک", "postcode": "1234567890", "payment": "درگاه بانکی", "note": "",
"items": [{"name": "شال نخی", "link": "https://mysite.ir/p/12", "qty": 2, "total": 400000}]}
]}
tracking در صورت ثبت: {"carrier": "post", "carrierName": "پست", "code": "1234…", "url": "", "date": "…"}.
GET order?id=1002
همان ردیف فهرست بهاضافه جزئیات؛ سفارش ناموجود: {"ok": false, "error": "سفارش پیدا نشد."}
{"ok": true, "data": { …ردیف فهرست…,
"email": "", "shippingMethod": "پست پیشتاز", "shippingTotal": 50000, "discount": 0, "subtotal": 400000,
"billing": {"name": "سارا محمدی", "phone": "0912…", "state": "تهران", "city": "تهران", "address": "ونک…", "postcode": ""},
"shipping": {"name": "…", "phone": "", "state": "…", "city": "…", "address": "…", "postcode": ""},
"lines": [{"name": "شال نخی", "link": "…", "sku": "SH-1", "meta": "رنگ: آبی", "qty": 2, "unit": 200000, "total": 400000}],
"notes": [{"id": 5, "text": "…", "customer": false, "by": "…", "date": "…"}],
"carriers": {"post": "پست", "tipax": "تیپاکس", "chapar": "چاپار", "courier": "پیک", "other": "سایر"}}}
POST order-status
{"id": 1002, "status": "completed"} → {"ok": true, "id": 1002, "status": "completed"}
لغو سفارش باید موجودی کمشده را برگرداند (مثل ووکامرس).
۶. امکانهای اختیاری
هر کدام را پیاده کردید، نامش را در features پاسخ ping بیاورید. شکل ورودی و خروجی همان افزونه وردپرس فروشیکس است؛ مهمترینها:
| نام در features | امکان | عملیاتها |
|---|---|---|
stats | آمار داشبورد | GET stats |
tracking | کد رهگیری | POST order-tracking |
notes | یادداشت سفارش | POST order-note |
manual-orders | ثبت سفارش دستی | POST order-create |
products-edit | ساخت و ویرایش محصول | GET product POST product POST products-bulk POST media GET brands GET site-info |
customers | فهرست مشتریان | GET customers |
reports | گزارش فروش بازه | GET report |
visits | آمار بازدید | GET visits |
coupons | کد تخفیف | GET coupons POST coupon POST coupons-bulk POST coupon-action |
sales | حراج گروهی | GET sales POST sale-apply POST sale-clear |
events | اعلان سفارش تازه و موجودی کم | GET events |
history | تاریخچه تغییر قیمت | GET history |
POST order-tracking (tracking)
{"id": 1002, "carrier": "post", "code": "123456789", "complete": true} → {"ok": true, "data": {…جزئیات سفارش…}}
GET events?after=1001&low=5 (events)
برای اعلان سفارش تازه و موجودی کم (هر ۲ دقیقه). after=0 یعنی فقط maxOrderId را برگردان.
{"ok": true, "maxOrderId": 1003, "more": false,
"orders": [ …ردیف کامل سفارشهای با شناسه بزرگتر از after (حداکثر ۵۰)… ],
"lowStock": [{"id": 12, "parentId": 0, "name": "شال نخی", "qty": 2}]}
GET report?period=30d (reports)
period: month (ماه شمسی جاری)، prev_month، 30d، 90d، year (سال شمسی)، custom با from و to. فقط سفارشهای فروش (پرداختشده و لغونشده).
{"ok": true, "period": "30d", "label": "30 روز اخیر", "from": "2026-09-05", "to": "2026-10-04", "prevFrom": "…", "prevTo": "…",
"summary": {"sales": 12500000, "orders": 31, "items": 58, "shipping": 900000, "discount": 300000, "gross": 12800000,
"refunds": 300000, "feeDiscount": 0, "refundOther": 0, "avg": 403226},
"prev": { …همان برای بازه قبل… },
"daily": [{"day": "2026-09-05", "sales": 400000, "orders": 1}, …], "prevDaily": [ … ],
"products": [{"id": 12, "parentId": 0, "name": "شال نخی", "qty": 9, "revenue": 1800000, "orders": 7}]}
GET customers (customers)
{"ok": true, "data": [{"key": "09121112233", "name": "سارا محمدی", "phone": "09121112233", "email": "", "state": "تهران",
"city": "تهران", "orders": 4, "total": 1800000, "first": "2026-03-01T…", "last": "2026-10-04T…"}]}
برای جزئیات بقیه امکانها (آمار داشبورد، محصول، کد تخفیف، حراج و…) کد افزونه وردپرس فروشیکس را ببینید یا با پشتیبانی تماس بگیرید.
۷. سنجش و اتصال
- در فروشیکس وارد شوید و ابزار سنجش اتصال را باز کنید؛ نشانی پایه و کلید را بدهید. امنیت امضا، شکل پاسخ هسته و امکانهای اعلامشده بررسی میشود و هر ایراد با توضیح فارسی نشان داده میشود. هیچ دادهای روی سایت شما عوض نمیشود.
- وقتی همه موارد درست بود، فروشنده در «فروشگاهها ← افزودن فروشگاه ← سایت دیگر (API)» نشانی و کلید را وارد میکند.
- اگر بعداً امکان تازهای اضافه کردید، فروشنده دکمه «بهروزرسانی امکانها» را در فهرست فروشگاهها میزند.
فروشگاهساز هستید و میخواهید همه مشتریهایتان یکجا وصل شوند؟ با ما تماس بگیرید.