واجهة برمجة تطبيقات المورد: تسجيل الطلبات اليدوية دون منافسة نفسك
النسخة المنشورة من هذا الدليل طلبت منك استخدام GET /orders. هذا الـ endpoint يعطي نفس الطلب لكل من يستعلم عنه. هناك endpoint يقوم بحجز الطلب بشكل ذري، وlease يجب أن تأخذه بنفسك، وحدّان لمعدل الاستخدام لا يوثّقهما أحد. إليك الحلقة التي تعمل فعلاً تحت الضغط.
Sarah Johnsonإذا كنت تنفّذ الطلبات اليدوية عبر API، فإن أول قرار هو أي نقطة نهاية (Endpoint) تجمع العمل، وهو القرار الذي تخطئ فيه معظم عمليات الدمج. GET /orders هي عملية قراءة. إنها تُرجع الطلبات المطابقة لمن يسأل ولا تغيّر شيئًا. شغّل عاملين (Workers)، أو عاملًا واحدًا مع إعادة محاولة، وكلاهما سيحصل على نفس الطلب وكلاهما سيحاول تسليمه.
نقطة النهاية التي تستحوذ على العمل هي POST /orders-pull. إنها تختار طلباتك اليدوية المعلّقة، وتقلب كل واحد من "معلّق" إلى "قيد المعالجة" عبر عملية مقارنة وتعيين (Compare and Set)، وتُرجع فقط الطلبات التي نجح قلبها فعليًا. إذا طلب عامل ثانٍ بعد جزء من الألف من الثانية، تفشل المقارنة ولا يظهر ذلك الطلب في استجابته. ملاحظة المصدر الخاصة بها تصفها بأنها متوافقة مع PerfectPanel، وهو الشكل الذي تتوقعه معظم عمليات دمج البانيلات بالفعل.
الحلقة، بالترتيب الذي يجب أن تعمل به
- الاستحواذ (Claim).
POST /orders-pull. جسم اختياريlimit، الافتراضي 100، وبحد أقصى صارم 500. الأقدم أولًا. - الاستئجار (Lease)، إذا كان عامل التنفيذ بطيئًا.
PATCH /orders/{id}/last-process-timeبدون جسم يختم الوقت الحالي، مما يخفي الطلب عن استطلاعاتك الخاصة لمدة ثلاثين دقيقة. - التسليم.
POST /orders-updateمع أسطر بيانات الاعتماد، أوPOST /orders/{id}/accounts-bulkلأي شيء كبير. - الإغلاق. نفس استدعاء
orders-updateيحملstatus: "completed"، أو"partial"إذا قمت بتعبئة جزء منه.
المصادقة هي ترويسة (Header) في كل استدعاء، وهناك شكلان مقبولان:
curl -X POST "https://api.hstockplus.com/api/admin/v2/orders-pull" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
# مكافئ
curl -X POST "https://api.hstockplus.com/api/admin/v2/orders-pull" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
أنشئ المفتاح من صفحة إعدادات API في لوحة التحكم الخاصة بك. غياب الترويسة يُرجع API_KEY_REQUIRED؛ المفتاح الخاطئ يُرجع INVALID_API_KEY. هذان رمزان مختلفان ويعنيان أخطاء مختلفة.
نافذة الثلاثين دقيقة هي استئجار (Lease)، وهي اختيارية
تتخطى نقطتا نهاية القائمة أي طلب يكون lastProcessTime الخاص به داخل آخر ثلاثين دقيقة. يبدو ذلك كإزالة تكرار تلقائية وهو ليس كذلك، لأن أيًا من النقطتين لا يكتب هذا الحقل. لا شيء يكتبه سواك، عبر PATCH /orders/{id}/last-process-time.
إذًا الحقل هو استئجار تعاوني. اختمه عندما تلتقط طلبًا وسيختفي من استطلاعك التالي لمدة نصف ساعة، وهذا ما تريده إذا كان التسليم يستغرق وقتًا وكان جهاز الاستطلاع يعمل كل دقيقة. أرسل طابعًا زمنيًا ISO صريحًا لتعيين انتهاء مختلف، أو null لتحريره فورًا. تجاهل نقطة النهاية تمامًا ولن تنطبق عليك النافذة أبدًا.
orders-pull يحميك بالفعل من الازدواجية في الاستحواذ عبر قلب الحالة. الاستئجار هو لوضع الفشل الثاني: عامل التنفيذ أخذ الطلب، وما زال يعمل، ولا تريد رؤيته مجددًا في هذه الأثناء.
قراءة الطلبات، مع افتراضات الفلترة التي تفاجئ الناس
GET /orders لا تزال نقطة النهاية الصحيحة للتسوية (Reconciliation)، والاسترجاع (Backfill)، ولوحات المعلومات. هناك افتراضان من افتراضاتها يستحقان المعرفة قبل البناء عليها.
- حالة الدفع الافتراضية قيمتان، وليس واحدة. بدون معامل
paymentStatusتحصل على الطلبات التي تكونcompletedأوpartial. "جزئي" تعني مستردًا جزئيًا، وهذه الطلبات ما زالت عملًا حيًا. المعامل نفسه يقبل قائمة مفصولة بفواصل من pending, completed, failed, refunded, processing و partial. - نوع المنتج اختياري وجمع. ليس مطلوبًا للطلبات اليدوية. يقبل
manualوinventoryوauto، مفصولة بفواصل، لذاproductType=manual,autoصالح.
فلتر حالة الطلب يقبل pending, processing, completed, refunded و error. partial ليس من بينها؛ إنها حالة دفع. فلاتر أخرى: entityType كـ product أو smm_service، subCategory بالاسم، createdFrom و createdTo كطوابع ISO زمنية، limit حتى 500، و offset.
curl -X GET "https://api.hstockplus.com/api/admin/v2/orders\
?status=pending,processing&productType=manual&paymentStatus=completed,partial&limit=50" \
-H "X-Api-Key: YOUR_API_KEY"
ماذا يحدث لأسطر بيانات الاعتماد التي ترسلها
هنا يأتي معظم تذاكر الدعم على API، لأن ثلاثة تحويلات تعمل قبل تخزين أي شيء ولا يمكن رؤية أي منها في الاستجابة.
- كل عنصر يُقسَّم على أسطر جديدة. يمكن لإدخال مصفوفة واحد أن يحمل ملفًا كاملًا.
["a:1\nb:2\nc:3"]و["a:1","b:2","c:3"]هما نفس الإرسال. الأسطر الفارغة والمسافات المحيطة تُحذف. - التكرارات تُسقط، دون حساسية لحالة الأحرف. داخل الدفعة، وضد كل ما تم تسليمه بالفعل على ذلك الطلب. إعادة إرسال نفس الدفعة بعد مهلة زمنية آمنة ولا تسلّم شيئًا مرتين.
- الأسطر الإضافية التي تتجاوز كمية الطلب تُتجاهل بصمت. يُقتطع الإرسال إلى الفتحات المتبقية. لا يُرجع خطأ، لذا أرسل العدد الصحيح.
ما يحتويه السطر هو قرارك بالكامل. المنصة تخزنه كسلسلة نصية واحدة غير شفافة ولا تحلله أبدًا، لذا زوج مفصول بنقطتين، أو ثلاثي مفصول بأنابيب مع سر مصادقة ثنائية، أو رمز مميز (Token)، أو كتلة جلسة: كلها مجرد نص. أي اصطلاح يعد به وصف قائمتك هو الاصطلاح الذي سيتوقعه المشتري، ولا شيء يفرضه نيابة عنك.
لأن التكرارات ضد عمليات التسليم السابقة تُسقط بدلًا من رفضها، فإن التسليم الجزئي عبر عدة استدعاءات هو نمط مدعوم. أرسل ما لديك، وعلّم الطلب partial، وأرسل الباقي لاحقًا، ثم علّمه completed.
حدان لمعدل الطلبات، كلاهما غير موثقين حتى الآن
orders-update يحمل حدًا أدنى للفاصل الزمني لكل مفتاح API، 200 مللي ثانية افتراضيًا، وحدًا أقصى للتزامن لكل مستخدم بطلبين قيد التنفيذ. تجاوز أي منهما يُرجع HTTP 429 مع تأخير قابل للقراءة آليًا:
{ "error": "RATE_LIMIT", "message": "Too many order update requests",
"retry_after_ms": 137 }
{ "error": "CONCURRENCY_LIMIT", "message": "Too many concurrent sync requests",
"retry_after_ms": 5000 }
اقرأ retry_after_ms وانتظر لمدة ذلك. التراجع الثابت سيكون إما بطيئًا جدًا لحالة الـ 200 مللي ثانية أو سريعًا جدًا لحالة التزامن. هناك أيضًا قفل استبعاد متبادل (Mutex) لكل طلب: تسليمان متسابقان على نفس الطلب يعطيان ORDER_DELIVERY_BUSY، وهو أيضًا 429، والاستجابة الصحيحة هي إعادة محاولة قصيرة بدلًا من إعادة إرسال بيانات اعتماد مختلفة.
رموز خطأ تستحق المعالجة بشكل منفصل
API_KEY_REQUIREDوINVALID_API_KEY: لا ترويسة مقابل ترويسة لا يتعرف عليها الكاش (Cache).ORDER_NOT_FOUND: معرف الطلب ليس في الكاش. لاحظ أن المورد العادي يمكن أيضًا ألا يُرى له شيء لطلب تلقائي مُنشأ حديثًا بسبب تأخير رؤية قصير، لذا 404 بعد ثوانٍ من الإنشاء ليس بالضرورة دائمًا.ACCESS_DENIED: الطلب لا يحتوي على أي سطر يخصك.PAYMENT_NOT_COMPLETED: غير مدفوع أو مسترد بالكامل. لا يمكنك التسليم فيه ولا يجب أن تعيد المحاولة.INVALID_ACCOUNTS، رسالة "No valid accounts provided": المصفوفة انبسطت إلى لا شيء. عادةً سلسلة فارغة أو مصفوفة من غير السلاسل النصية.RATE_LIMITوCONCURRENCY_LIMITوORDER_DELIVERY_BUSY: تراجع وأعد محاولة نفس الطلب.
حقلان لمسك الدفاتر الخاص بك
orders-update يقبل external_id لمرجع طلبك الداخلي، ويقبل أيضًا supplierOrderId كاسم مستعار إذا كان هذا هو الشكل الذي يرسله بانيلك بالفعل. يقبل external_price، رقمًا يسجل ما كلفك الطلب من المورد. هذا الرقم يُخزن كمرجع ولا يمس إجماليات المنصة. إرسال null أو سلسلة فارغة يترك ما هو موجود بالفعل، لذا التحديثات المتكررة لن تمحوه.
ماذا يعني "يدوي" فعليًا، وكم هو سريع حقًا
نوع المنتج "يدوي" يعني أن المورد يرفع البضاعة بعد وصول الطلب. إنه ليس إعدادًا لسرعة التسليم، والسجل يقول ذلك بوضوح. عبر كل طلبات المنتجات اليدوية المكتملة على الموقع، وعددها 6,552، متوسط الوقت من الدفع إلى الاكتمال أقل من ثلاثين ثانية، وثلاثة أرباعها تنتهي خلال عشر دقائق، وواحد فقط من كل أربعين تقريبًا يستغرق أكثر من يوم.
طلبات المخزون (Inventory) فورية فعليًا لأن الصفوف موجودة بالفعل. الطلبات التلقائية (Auto) تقع بين الاثنين. سبب ظهور اليدوي شبه سريع هو تحديدًا أن الموردين الذين يحققون حجمًا كبيرًا عليه يشغلون هذا الـ API على استطلاع قصير بدلًا من مراقبة لوحة تحكم. إذا كانت عمليتك تضيف دقائق، فأنت الذيل البطيء، وليس القاعدة.
ساعة واحدة لتعرفها إذا كنت تبيع خدمات نمو بدلًا من حسابات: طلب خدمة النمو يحصل على نافذة ما بعد بيع مدتها ثلاثة أيام بالضبط من لحظة تسليمه، بغض النظر عما يقوله الوصف. قوائم الحسابات تحمل ضمانها الخاص بدلًا من ذلك، محددًا لكل قائمة.
عامل تنفيذ يتصرف بشكل صحيح
const BASE = 'https://api.hstockplus.com/api/admin/v2';
const HEAD = { 'X-Api-Key': process.env.API_KEY, 'Content-Type': 'application/json' };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function call(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: HEAD,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json().catch(() => ({}));
if (res.status === 429) {
await sleep(json.retry_after_ms ?? 1000);
return call(method, path, body);
}
if (!res.ok) throw Object.assign(new Error(json.message || res.status), { code: json.error });
return json;
}
async function tick() {
const { orders = [] } = await call('POST', '/orders-pull', { limit: 50 });
for (const order of orders) {
// الطلب الآن 'processing' وهو ملكك. خذ استئجارًا إذا كنت بطيئًا.
await call('PATCH', '/orders/' + order.id + '/last-process-time', {});
const accounts = await prepareCredentials(order); // نظامك
await call('POST', '/orders-update', {
order: order.id,
status: accounts.length >= order.quantity ? 'completed' : 'partial',
accounts,
supplierOrderId: 'SUP-' + order.id,
});
}
}
ثلاثة أشياء تفعلها هذه الحلقة ولم يفعلها الشكل الأقدم: إنها تستحوذ بدلًا من أن تقرأ، وتحترم تأخير إعادة المحاولة الخاص بالخادم بدلًا من تخمين واحد، وتُبلغ عن partial بصدق بدلًا من إغلاق طلب لم تستطع تعبئته.
مرجع نقطة النهاية الكامل، بما في ذلك إنشاء المنتج، والتذاكر، والاستردادات، ووظيفة الحسابات بالجملة، موجود في صفحة API للموردين. إذا كنت لا تزال تقرر ما إذا كانت القائمة يجب أن تكون يدوية أو مخزونًا أو تلقائية في المقام الأول، فهذا الخيار مغطى في دليل إدارة المنتجات للموردين.
```


