إعادة محاولة الطلبات بأمان في واجهات B2B 2026 — نمط التحقق من الحالة قبل إعادة المحاولة
مفاتيح Idempotency هي التقنية التي تجعل أي طلب POST في واجهة B2B آمناً لإعادة المحاولة. من دونها، يتحوّل كل انتهاء مهلة شبكية على العميل إلى سؤال "هل أُنشئ الطلب أم لا؟" — وفي 30% من الحالات تكون الإجابة "كلاهما". واجهة FoxReload لا تدعم ترويسة Idempotency-Key. تشرح هذه المقالة النمط البديل الصحيح — التحقق من الحالة قبل إعادة المحاولة — إلى جانب استراتيجيات إلغاء التكرار من جانب العميل.
1. لماذا لا تملك واجهة FoxReload مفاتيح Idempotency
نقطة نهاية الطلبات في FoxReload عند POST /api/orders لا تقبل ترويسة Idempotency-Key. كل استدعاء يُنشئ طلباً جديداً. هذا يعني:
- إعادة محاولة واحدة بعد خلل شبكي قد تُنشئ طلبين
- يُخصم رصيدك مرتين
- تُسلَّم مجموعتان من الأكواد
الحل بالكامل على جانب العميل.
2. نمط التحقق من الحالة قبل إعادة المحاولة
قبل إعادة محاولة POST /api/orders الذي أرجع انتهاء مهلة أو خطأً غامضاً، تحقّق مما إذا كان الطلب موجوداً بالفعل:
async function safeCreateOrder(
items: OrderItem[],
apiKey: string,
): Promise<Order> {
// Step 1: Record intent in your own DB before calling the API
const clientRef = randomUUID();
await db.pendingOrders.insert({ clientRef, items, createdAt: new Date() });
try {
const res = await fetch('https://public-api.foxreload.com/api/orders', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const order = await res.json();
await db.pendingOrders.update(clientRef, { orderId: order.id });
return order;
} catch (err) {
// Step 2: On failure, check if the order was created
const existing = await db.pendingOrders.findByRef(clientRef);
if (existing?.orderId) {
// Order was created; poll its status
return pollOrder(existing.orderId, apiKey);
}
// Step 3: Check recent FoxReload orders for a match
const recent = await fetchRecentOrders(apiKey); // GET /api/orders?limit=10
const match = findMatchingOrder(recent, items);
if (match) {
await db.pendingOrders.update(clientRef, { orderId: match.id });
return match;
}
// Step 4: Only retry if confirmed not created
throw err; // caller can retry after delay
}
}
3. تخزين إلغاء التكرار من جانب العميل
الفكرة الأساسية — خزّن نية الطلب قبل إجراء استدعاء الواجهة. هذا يمنحك سجلاً مرجعياً للتحقق منه عند الفشل.
| التخزين | الكمون | TTL | التكلفة / مليون سجل |
|---|---|---|---|
| Postgres UNIQUE | 5–8ms | ∞ | $0.10 |
| Redis SETNX | <2ms | 24h | $0.40 |
| DynamoDB ConditionExpression | 8–12ms | 24h | $1.25 |
| Memcached CAS | <1ms | 24h | $0.30 |
بالنسبة لمعظم التكاملات، يكون Postgres مع قيد UNIQUE على client_ref كافياً ويمنحك مساراً تدقيقياً دائماً.
4. مقارنة مناهج إلغاء التكرار
| المنهج | يمنع التكرار | المسار التدقيقي | كتابة DB إضافية؟ |
|---|---|---|---|
| Idempotency-Key (غير متاح في FoxReload) | نعم | على الخادم | لا |
| التحقق من الحالة قبل إعادة المحاولة | نعم | قاعدة بياناتك | نعم |
| بوابة SETNX من جانب العميل | نعم (ضمن TTL) | Redis | نعم |
| إعادة محاولة عمياء (لا تفعل ذلك أبداً) | لا | لا | لا |
يضيف منهج التحقق من الحالة كتابة DB واحدة لكل محاولة طلب. عند أي حجم أقل من ملايين الطلبات يومياً، يكون هذا مقبولاً تماماً.
5. مقارنة خلفيات تخزين إلغاء التكرار
// Postgres approach (recommended for audit)
await db.query(`
INSERT INTO order_intents (client_ref, items_hash, created_at)
VALUES ($1, $2, NOW())
ON CONFLICT (client_ref) DO NOTHING
`, [clientRef, hashItems(items)]);
6. ماذا لو واجهت تسابقاً بين طلبين متزامنين؟
إذا حاول عاملان إنشاء الطلب نفسه في آنٍ واحد، فإن قيد UNIQUE في قاعدة البيانات على client_ref سيرفض أحدهما على مستوى DB قبل أن يصل أيٌّ منهما إلى FoxReload. هذا هو الحل الأنظف:
CREATE TABLE order_intents (
client_ref UUID PRIMARY KEY,
order_id UUID,
items_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMPTZ
);
يتابع المُدخِل الأول وحده استدعاء الواجهة. أما الثاني فيقرأ السجل الموجود ويراقب النتيجة.
CTA
واجهة FoxReload لا تملك مفاتيح Idempotency — طبّق التحقق من الحالة قبل إعادة المحاولة ومخزن client_ref في نظامك الخاص. مرجع واجهة الطلبات الكامل متاح بعد التهيئة — اطلب الوصول.
