تكاملات الموردين

اربط مورّدًا جديدًا مثل تاجر أو صفقة أو علي إكسبريس.

إيه اللي بيعمله تكامل المورّد

تكامل المورّد بيخلّي التاجر يربط مورّد دروبشيبنج خارجي — زي تاجر أو صفقة أو علي إكسبريس — وبعدها:

  1. يربط حساب المورّد بتاعه (بيانات الاعتماد تُخزَّن مشفّرة).
  2. يستورد منتجات من كتالوج المورّد لمتجره.
  3. يرسل الطلبات للمورّد تلقائيًا لما العميل يشتري.
  4. يزامن المخزون والأسعار وحالة الشحن/التنفيذ رجوعًا.

هو بلاجن عادي (plugins/{your-slug}-supplier/) — الأجزاء الخاصة بالمورّد هي موديل اتصال، مستورد كتالوج، دافع طلبات، وربط شحن. القسم ده يوريك الشكل اللي أي مورّد جديد يتبعه، عشان تضيف مورّدك بنفس الطريقة اللي عملها تاجر وصفقة وعلي إكسبريس.

موديل الاتصال

كل تاجر ياخد صف اتصال واحد مربوط بـuser_id بتاعه. سكيما حقيقية من taager_connections:

Schema::create('yoursupplier_connections', function (Blueprint $table): void {
    $table->id();
    $table->foreignId('user_id')->unique()->constrained()->cascadeOnDelete();
    $table->string('email');
    $table->text('credentials');                       // مشفّرة — أبدًا نص صريح
    $table->string('country', 10)->default('EGY');
    $table->string('markup_type', 20)->default('percentage');
    $table->decimal('markup_value', 10, 2)->default(0);
    $table->boolean('is_active')->default(true);
    $table->timestamp('connected_at')->nullable();
    $table->timestamps();
});

خزّن التوكن/بيانات الاعتماد مشفّرة (cast العمود لـencrypted)، واقصر كل استعلام بـuser_id عشان تاجر ما يقدرش يشوف بيانات تاجر تاني.

الراوتس وواجهة التاجر

سجّل راوتساتك مقصورة على التجار اللي فعّلوا البلاجن. مجموعة راوتس حقيقية من تاجر:

Route::middleware(['web', 'auth', 'verified', 'user', 'store.setup', 'plugin.access:yoursupplier-supplier'])
    ->prefix('user/suppliers/yoursupplier')
    ->name('yoursupplier.')
    ->group(function (): void {
        Route::get('/connect',  [ConnectionController::class, 'show'])->name('connect');
        Route::post('/connect', [ConnectionController::class, 'store'])->name('connect.store');
        Route::delete('/connect',[ConnectionController::class, 'destroy'])->name('disconnect');

        Route::get('/catalog',        [CatalogController::class, 'index'])->name('catalog');
        Route::post('/catalog/import',[ImportController::class, 'store'])->name('import');

        Route::get('/settings',  [SettingsController::class, 'edit'])->name('settings');
        Route::put('/settings',  [SettingsController::class, 'update'])->name('settings.update');

        Route::post('/shipping/sync', [ShippingController::class, 'sync'])->name('shipping.sync');

        Route::get('/orders',           [OrdersController::class, 'index'])->name('orders');
        Route::post('/orders/{id}/sync',[OrdersController::class, 'sync'])->name('orders.sync');
    });

أضف عنصر sidebar تحت الأب المشترك suppliers عشان كل الموردين يتجمّعوا مع بعض:

$registry->registerUserItem('catalog', [
    'parent_key'  => 'suppliers',
    'key'         => 'yoursupplier-supplier',
    'icon'        => 'truck',
    'label'       => 'مورّدك',
    'route_name'  => 'yoursupplier.connect',
    'active_routes' => ['yoursupplier.*'],
    'plugin_slug' => 'yoursupplier-supplier',
]);

استيراد الكتالوج

اجلب المنتجات من API المورّد، اربطها بمنتجات النواة (العنوان، الصور، السعر + markup التاجر)، واحتفظ بجدول ربط منتجات يربط معرّف منتج المورّد بمعرّف منتج النواة المُنشَأ (والدولة/المتغير). الاستيرادات الكبيرة تمشي عبر مهمة مجدولة (queued job) عشان التاجر ما يتعطّلش.

إرسال الطلبات

راقب الطلبات الجديدة (أو شغّل مهمة مزامنة مجدولة) وابعت الطلب للمورّد لما يتأكد. احتفظ بجدول ربط طلبات عشان ما تبعتش نفس الطلب مرتين (idempotency)، وعشان تقدر تزامن حالة التنفيذ رجوعًا.

ربط المواقع — external_mapping

الموردون يتوقعوا أسماء الدول/المدن/المناطق بتاعتهم، اللي نادرًا تطابق أسماء المنصة. المنصة تخزّن عمود JSON اسمه external_mapping على countries وshipping_regions وshipping_cities لده بالظبط. اقرأ معرّف/اسم المورّد منه عند بناء الطلب:

$supplierCityId = $city?->external_mapping['yoursupplier_city_id']
    ?? $region?->external_mapping['yoursupplier_city_id'];

دي الطريقة اللي بيها المنصة تبعت الموقع الصحيح لعلي إكسبريس/تاجر/صفقة من غير جدول ترجمة ثابت.

الـWebhooks — افشل مغلق

لو المورّد بيرجع نداء (حالة طلب، مخزون)، راوت الـwebhook بتاعك (صلاحية webhook_listener) لازم يوثّق قبل أي حاجة:

  • استخدم سر لكل اتصال، مش سر عالمي واحد مشترك، عشان سر متسرّب ما يقدرش يأثّر على كل التجار (كده صفقة بتقصر webhooks بتاعتها لكل اتصال).
  • ارفض لما السر/التوقيع ناقص أو غلط — افشل مغلق، ما تفشلش مفتوح أبدًا.
  • اقصر الحمولة على المستأجر صاحب السر ده قبل ما تكتب أي حاجة.

حارس العملة

الاستيراد بيربط كتالوج التاجر بدولة/عملة مورّد. لو بتدعم دول متعددة، احرس ضد إن التاجر يغيّر عملة المتجر وهو عنده منتجات مورّد مستوردة — تاجر بيمنع تغيير العملة بخطأ تحقق لحد ما المنتجات المستوردة تتشال. اتبع نفس النمط عشان المنتجات المستوردة ما تشاورش على دولة غلط.

قائمة المراجعة

  • اتصال مشفّر واحد لكل user_id؛ كل استعلام مقصور على التاجر.
  • جداول ربط منتجات + ربط طلبات لاستيراد/إرسال idempotent.
  • أسماء المواقع تُحَلّ عبر external_mapping، مش ثابتة.
  • الـwebhooks موثّقة لكل اتصال وتفشل مغلق.
  • الاستيرادات/الإرسالات الطويلة تشتغل كمهام مجدولة.