دليل تقني

التجارة متعددة القنوات: هندسة Checkout موحد عبر خمسة موردين

كيف تبني checkout موحد عبر موردين متعددين. مزامنة ثنائية الاتجاه، بروكسي توفر بالوقت الفعلي، تحديد نطاق القنوات، توجيه الطلبات، ومعالجة الأخطاء لما الموردين يفشلون بنص الـ checkout.

16 مارس 202614 دقيقة للقراءةفريق هندسة أورنتس

Multi-Channel مش "ربط بأمازون"

أغلب مقالات Multi-Channel Commerce بتوصف كيف تربط متجرك بـ APIs السوق. هاد syndication، مش هندسة. Multi-Channel Commerce الحقيقي يعني: موردين متعددين بـ APIs مختلفة، نماذج تسعير مختلفة، صيغ توفر مختلفة، وتدفقات حجز مختلفة، كلها بتنعرض للعميل كـ checkout واحد سلس.

بنينا منصة تجميع موردين بتوحد الـ checkout عبر 5 APIs لموردين. سلة واحدة، خمس موردين، طلب واحد. هالمقال بيغطي الهندسة يلي بتخلي هالشي يشتغل. للتقييم الأوسع لمنصات التجارة، شوف دليل Vendure للإنتاج ودليل منصات التجارة الإلكترونية.

مشكلة الـ Checkout الموحد

كل مورد عنده API خاصته، صيغة بيانات خاصته، نموذج توفر خاصته، وتدفق حجز خاصته:

الجانبمورد Aمورد Bمورد C
نمط الـ APIREST v2GraphQLSOAP/XML
التسعيرلكل شخص، ديناميكيسعر ثابت، شرائحلكل مجموعة، متفاوض عليه
التوفرAPI بالوقت الفعليملف مزامنة يوميWebhook عند التغيير
الحجزخطوتين (حجز + تأكيد)خطوة واحدة (حجز فوري)ثلاث خطوات (عرض سعر + حجز + تأكيد)
الإلغاءمجاني لغاية 24 ساعةغير قابل للاستردادسياسة استرداد جزئي
صيغة الـ IDUUIDرقميبادئة أبجدية رقمية

الـ checkout الموحد لازم ينورمل كل هاد ورا interface واحد. العميل بيضيف منتجات من موردين مختلفين لسلة واحدة وبيدفع مرة وحدة. النظام بيوجه كل منتج للمورد الصح، بيعالج تدفق الحجز لكل مورد، وبيعرض تأكيد واحد للعميل.

Supplier Adapter Pattern

كل مورد بياخد adapter بينفذ interface مشترك:

interface SupplierAdapter {
    search(query: SearchQuery): Promise<Product[]>;
    checkAvailability(productId: string, date: string, persons: PersonConfig[]): Promise<AvailabilityResult>;
    reserve(params: ReservationParams): Promise<Reservation>;
    confirm(reservationId: string, bookerInfo: BookerInfo): Promise<BookingConfirmation>;
    cancel(bookingId: string): Promise<CancellationResult>;
}

// كل مورد بينفذ الـ interface بطريقة مختلفة
class SupplierAAdapter implements SupplierAdapter {
    async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
        // مورد A: استدعاء API بالوقت الفعلي
        const response = await this.httpClient.get(`/v2/availability/${productId}`, {
            params: { date, adults: persons.filter(p => p.type === 'adult').length },
        });
        return this.normalizeAvailability(response.data);
    }
}

class SupplierBAdapter implements SupplierAdapter {
    async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
        // مورد B: استعلام GraphQL
        const { data } = await this.graphqlClient.query({
            query: AVAILABILITY_QUERY,
            variables: { productId, date },
        });
        return this.normalizeAvailability(data.availability);
    }
}

الـ adapter بينورمل استجابة كل مورد لصيغة مشتركة. سيرفس الـ checkout بيشتغل بالصيغة المشتركة، ولا مرة بالبنى الخاصة بالمورد.

بروكسي التوفر بالوقت الفعلي

بعض الموردين بيقدموا تسعير بالوقت الفعلي بيتغير كل دقيقة (تسعير ديناميكي، مخزون محدود). نتائج البحث بتعرض سعر مخزن بالـ cache، بس وقت الـ checkout النظام لازم يتأكد من السعر الحالي.

async function getAvailabilityWithFallback(
    productId: string, date: string, persons: PersonConfig[]
): Promise<AvailabilityResult> {
    const supplier = getSupplierForProduct(productId);

    if (supplier.supportsRealTimeAvailability) {
        try {
            // سعر مباشر من API المورد
            const live = await supplier.adapter.checkAvailability(productId, date, persons);
            await cache.set(`avail:${productId}:${date}`, live, { ttl: 300 });
            return live;
        } catch (error) {
            // API المورد معطل: رجع السعر المخزن مع تحذير
            const cached = await cache.get(`avail:${productId}:${date}`);
            if (cached) {
                return { ...cached, stale: true, warning: 'Price may have changed' };
            }
            throw new AvailabilityError('Cannot determine availability');
        }
    }

    // مورد بتوفر دفعات: رجع من الفهرس
    return searchIndex.getAvailability(productId, date);
}

الـ proxy pattern: جرب مباشر، ارجع للـ cache، ارجع للفهرس. دايماً خبر العميل إذا السعر ممكن يكون قديم.

المزامنة الثنائية: منع الحلقات اللامتناهية

لما نظامين بيزامنوا بيانات بالاتجاهين (نظام التجارة ونظام العمليات)، كل تحديث من نظام A بيطلق مزامنة لنظام B، يلي بدوره بيطلق مزامنة راجعة لـ A.

// تتبع المصدر بيمنع الحلقات اللامتناهية
interface SyncMessage {
    entityId: string;
    entityType: string;
    data: any;
    source: string;           // "commerce" | "operations" | "import"
    correlationId: string;
}

async function handleSync(message: SyncMessage) {
    // تجاهل الرسائل يلي مصدرها هالنظام
    if (message.source === THIS_SYSTEM_ID) {
        return; // ACK وتخطي
    }

    // عالج المزامنة
    await updateEntity(message.entityId, message.data);

    // انشر التحديث بعلامة المصدر تبعنا
    await publishSync({
        ...message,
        source: THIS_SYSTEM_ID,  // علمها عشان النظام الثاني يتجاهلها
    });
}

كل رسالة بتحمل حقل source. لما نظام بيستقبل رسالة من نفسه (عبر النظام الثاني)، بيتجاهلها. الحلقة بتنكسر عند أول round-trip.

لمزيد من أنماط الـ Event-Driven شاملة الـ deduplication والـ dead letter handling، شوف دليل هندسة الـ Event-Driven.

Channel Scoping: الظهور مقابل النشر مقابل التوفر

ثلاث مفاهيم لازم تبقى منفصلة:

المفهومالسؤالمثال
الظهورشو الموردين يلي هالقناة بتشوفهم؟الموقع الألماني بيشوف موردين A، B، C. الـ Partner API بيشوف بس مورد A.
النشرهالمنتج منشور ونشط؟المنتج موجود بس مش منشور (مسودة).
التوفرهالمنتج ممكن ينحجز هلق؟المنتج منشور بس نفذ ليوم السبت.
// القناة بتحدد الظهور
const channel = await channelStore.get(tenantId, channelId);
const visibleSupplierIds = channel.supplierIds;

// فلتر المنتجات حسب ظهور القناة
const products = await searchIndex.search({
    query: userQuery,
    filters: {
        supplier_id: { $in: visibleSupplierIds },  // تحديد نطاق القناة
        status: 'active',                           // النشر
    },
});

// التوفر بيتفحص وقت الـ checkout (concern منفصل)

منتج ممكن يكون ظاهر (بقائمة موردين القناة)، منشور (حالة = نشط)، بس مش متوفر (نفذ). كل واحد فلتر مختلف بمرحلة مختلفة من التدفق.

توجيه الطلبات

لما طلب فيه منتجات من موردين متعددين، كل منتج لازم يتوجه للمورد الصح للتنفيذ:

async function processMultiSupplierOrder(order: Order): Promise<OrderResult> {
    // تجميع المنتجات حسب المورد
    const itemsBySupplier = groupBy(order.items, item => item.supplierId);

    // معالجة منتجات كل مورد بشكل مستقل
    const results = await Promise.allSettled(
        Object.entries(itemsBySupplier).map(async ([supplierId, items]) => {
            const adapter = getAdapter(supplierId);

            // حجز
            const reservation = await adapter.reserve({
                items,
                booker: order.bookerInfo,
                expiresIn: 900, // 15 دقيقة حجز
            });

            // تأكيد
            return adapter.confirm(reservation.id, order.bookerInfo);
        })
    );

    // معالجة النتائج المختلطة
    const successful = results.filter(r => r.status === 'fulfilled');
    const failed = results.filter(r => r.status === 'rejected');

    if (failed.length > 0 && successful.length > 0) {
        // نجاح جزئي: بعض الموردين انحجزوا، وبعضهم فشلوا
        // نلغي الحجوزات الناجحة؟ أو نأكد الطلب الجزئي؟
        // هاي قرار تجاري، مش تقني.
        return handlePartialSuccess(order, successful, failed);
    }

    return { status: failed.length === 0 ? 'COMPLETED' : 'FAILED', results };
}

مشكلة النجاح الجزئي

شو بيصير لما مورد A بيأكد بس مورد B بيفشل؟ الخيارات:

الاستراتيجيةالسلوكالأفضل لـ
الكل أو لا شيإلغاء مورد A إذا B فشلطلبات عالية القيمة، فعاليات
تنفيذ جزئيتأكيد A، إعلام العميل عن Bمنتجات قابلة للاستبدال
إعادة محاولة ثم إلغاءإعادة محاولة B 3 مرات، إلغاء A إذا B فشل نهائياًتوازن بين UX والموثوقية

الاستراتيجية الصح بتعتمد على البزنس. لتذاكر الفعاليات (مش قابلة للاستبدال)، الكل أو لا شي عادةً هو الصح. للمنتجات القابلة للاستبدال، التنفيذ الجزئي أفضل.

معالجة الأخطاء: لما الموردين يفشلوا بنص الـ Checkout

نقطة الفشلشو صارالاستجابة
خلال البحثtimeout بـ API الموردعرض نتائج من الموردين الثانيين
خلال فحص التوفرسعر قديمعرض السعر المخزن مع تحذير
خلال الحجزالمخزون نفذحذف المنتج من السلة، اقتراح بدائل
خلال التأكيدالدفع مرفوض من الموردإلغاء الحجز، إعادة محاولة الدفع، أو استرداد
بعد التأكيدالمورد بعث إلغاءإعلام العميل، عرض إعادة حجز أو استرداد
خلال الإلغاءAPI المورد معطلوضع الإلغاء بالطابور لإعادة المحاولة

كل خطأ لازم يتعالج بشكل صريح. "صار خطأ ما" ما بتنقبل أبداً كرسالة خطأ للعميل بمعاملة تجارية.

أخطاء شائعة

  1. افتراض إنو كل الموردين عندهم نفس تدفق الحجز. تدفقات الخطوتين، الخطوة الواحدة، والثلاث خطوات كلها موجودة. الـ adapter لازم ينورملها.

  2. تجاهل تغيرات الأسعار بين البحث والـ checkout. التسعير الديناميكي يعني إنو سعر الـ checkout ممكن يختلف عن سعر البحث. لازم تتحقق وقت الـ checkout.

  3. ما في تتبع مصدر بالمزامنة الثنائية. بدونه، كل تحديث بيلف بشكل لا نهائي بين النظامين.

  4. معاملة ظهور القناة كـ UI concern. تحديد نطاق القناة لازم ينفرض على مستوى الـ query، مش بس بالـ frontend. مستهلكي الـ API بيشوفوا نفس الفلاتر.

  5. ما في استراتيجية لحالات الفشل الجزئي. طلبات الموردين المتعددين رح تفشل جزئياً. حدد سياسة البزنس قبل ما يصير هالشي بالإنتاج.

  6. استدعاءات متزامنة للموردين خلال الـ checkout. إذا مورد واحد بطيء، كل الـ checkout بيستنى. عالج الموردين بالتوازي مع timeouts فردية.

النقاط الأساسية

  • الـ Supplier Adapters بتنورمل التنوع. كل مورد بياخد adapter بينفذ interface مشترك. منطق البزنس ما بيلمس صيغ خاصة بالمورد أبداً.

  • التوفر بالوقت الفعلي هو proxy pattern. جرب مباشر، ارجع للـ cache، ارجع للفهرس. دايماً بلغ العميل عن حداثة البيانات.

  • تتبع المصدر بيمنع حلقات المزامنة. كل رسالة بتحمل مصدرها. الأنظمة بتتجاهل الرسائل يلي من نفسها.

  • الظهور، النشر، والتوفر ثلاث concerns منفصلة. القناة بتحدد الظهور. حالة المنتج بتحدد النشر. المخزون بيحدد التوفر. لا تخلطهم.

  • الفشل الجزئي قرار تجاري. الكل أو لا شي مقابل التنفيذ الجزئي بيعتمد على نوع المنتج. حدد السياسة قبل ما تبني الكود.

منبني أنظمة multi-channel commerce كجزء من ممارستنا بـ التجارة الإلكترونية والبرمجيات المخصصة. إذا بتحتاج مساعدة بهندسة تكامل الموردين، تواصل مع فريقنا أو اطلب عرض سعر.

المواضيع المغطاة

تجارة متعددة القنواتهندسة أومني تشانلتكامل السوقمزامنة التجارةcheckout موحدتجميع الموردينمزامنة ثنائية الاتجاه

جاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟

فريقنا متخصص في بناء أنظمة ذكاء اصطناعي جاهزة للإنتاج. خلينا نحكي كيف نقدر نساعد.

ابدأ محادثة