تخطَّ إلى المحتوى

الانتقال من HTTP اليدوي

نقل تطبيق يخاطب واجهة المنصّة أصلاً عبر fetch إلى الحزمة، طبقةً بعد طبقة، من دون كسر التثبيتات الحيّة.

آخر تحديث 9 أيلول 2026قراءة 3 د
في هذه الصفحة

هذه الصفحة للفرق التي يتكامل تطبيقها مع دكان أصلاً عبر استدعاءات fetch خاصة بها وتجديد رموز وفحص توقيع من صنعها. في نهايتها تكون لديك خطة مرحلية كل خطوة فيها نشر مستقل، وتبقى التثبيتات الحالية تعمل طوال الوقت، ولا يحتاج شيء مما خزّنته إلى إعادة كتابة.

ما يقابل ماذا#

ما كتبته ما تمنحك الحزمة ملاحظات
إعادة توجيه الموافقة مع PKCE وCookie حالة generatePkce وgenerateState وbuildAuthorizeUrl (أو مسار التثبيت في وحدة Nuxt) الرابط نفسه والمعاملات نفسها.
تبديل الكود exchangeCode تحمل الاستجابة الآن install_id وstore_id وstore_slug.
التجديد بمقارنة وتبديل على الرمز القديم TokenManager عبر TokenStore فيه withRefreshLock اقفل قبل التجديد بدلاً من السباق والتسوية بعده: تلغي المنصّة التثبيت عندما يُقدَّم رمز مدوَّر مرتين.
غلاف fetch بحامل مع إعادة محاولة عند 401 createDukkanClient يضيف إعادة المحاولة عند 429 و5xx مع Retry-After، وأخطاء منمّطة، وتصفّحاً، وIdempotency-Key على كل كتابة.
التحقق من HMAC على الجسم الخام verifyWebhookSignature أو المستقبِل كاملاً المخطط نفسه والترويسات نفسها، بوقت ثابت، على WebCrypto.
إزالة التكرار على معرّف التسليم InboxStore.insertIfNew على id الحدث الموقّع ترويسة التسليم خارج التوقيع؛ ومعرّف الحدث داخله.
ربط المتجر من أول غلاف موقّع install_id وstore_id من استجابة الرموز، وGET /installation للصفوف الأقدم منها لا انتظار بعد الآن لـ Webhook لتعرف هوية التثبيت.
نقطة نهاية Webhook لكل تثبيت نقطة نهاية واحدة؛ الغلاف يسمّي التثبيت تخدم وحدة Nuxt أيضاً اللاحقة الخاصة بكل تثبيت، فتبقى الروابط المسجّلة تعمل.

مرحلةً بعد مرحلة#

كل مرحلة نشر واحد ويمكن التحقق منها وحدها. مرّ تطبيق الإشعارات من دكان بهذه المراحل تحديداً.

١. تحقّق بالحزمة#

استبدل جسم دالة التحقق لديك بـ verifyWebhookSignature واحتفظ باختباراتك. المخطط هو مخطط المنصّة، مثبّت بـ متجهات الاختبار المشتركة، فنجاح الاختبارات يثبت الاستبدال.

TypeScript
import { verifyWebhookSignature } from "@dukkan.one/app-sdk/webhooks";

export async function verify(rawBody: Uint8Array, headers: Headers, secret: string) {
  const verdict = await verifyWebhookSignature({
    rawBody,
    timestamp: headers.get("x-dukkan-timestamp"),
    signature: headers.get("x-dukkan-hmac-sha256"),
    secret,
  });
  return verdict.ok;
}

٢. استدعِ عبر العميل#

أضف أعمدة الحزمة إلى جدول التثبيتات لديك (access_expires_at وrefresh_expires_at وreconnect_required؛ راجع الجدول المرجعي)، ووجّه createPostgresTokenStore إلى أسماء أعمدتك الحالية، ومرّر الاستدعاءات عبر العميل. لا يحتاج النص المشفّر الحالي إلى إعادة كتابة عندما تحتفظ بختّامك: createAesGcmSealer متوافق على السلك مع الغلاف الشائع iv.tag.ciphertext بصيغة base64، وأي صيغة أخرى تناسب واجهة SecretSealer ذات الدالتين.

احتفظ بمفاتيح الأمان من التكرار التاريخية حيث تهمّ:

TypeScript
await client.orders.updateStatus(orderId, "approved", {
  idempotency: { key: `confirm-${confirmationId}` },
});

فتعيد المحاولةُ العابرة للانتقال إجابةَ المنصّة المسجّلة بدلاً من الكتابة مرتين؛ تعيش الاستجابات المسجّلة 24 ساعة (Idempotency-Key).

٣. اعرف هوية كل تثبيت#

الصفوف المنشأة قبل أن تحمل استجابة الرموز المعرّفات لا تملك install_id. املأها مرة واحدة عند الإقلاع: لكل صف استدعِ GET /installation برمز الوصول المخزّن (وجدّد مرة إن رُفض) واكتب install.id وstore.id فيه. منذئذٍ يبحث المستقبِل عن التثبيتات بالمعرّف الموجود داخل الغلاف الموقّع.

install-response.jsonJSON
{
  "data": {
    "install": {
      "id": "8b2c1d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
      "app_id": "5f1e2d3c-4b5a-4c6d-8e7f-9a0b1c2d3e4f",
      "status": "active",
      "granted_scopes": [
        "orders:read",
        "orders:write"
      ],
      "effective_scopes": [
        "orders:read",
        "orders:write"
      ],
      "distribution_channel": "public",
      "installed_at": "2026-09-09T08:15:30.000Z",
      "api_version": "2026-09-09",
      "webhook": {
        "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
        "endpoint_url": "https://app.example.com/dukkan/webhooks",
        "topics": [
          "order.created",
          "order.paid",
          "app.uninstalled"
        ],
        "status": "active",
        "consecutive_failures": 0,
        "activated_at": "2026-09-09T08:15:31.000Z",
        "disabled_at": null
      }
    },
    "store": {
      "id": "3065a1f2-0c4d-4e8b-b7a9-5d2f8c1e9a44",
      "slug": "sham-perfumes",
      "name": "عطور الشام",
      "currency": "SYP",
      "decimals": 0,
      "locale": "ar",
      "timezone": "Asia/Damascus",
      "country": "SY",
      "is_development": true
    }
  }
}

الصف الذي يجيب تجديده بـ invalid_grant ميت: علّمه على أنه يحتاج إعادة اتصال ودع التاجر يثبّت من جديد.

٤. ركّب مسارات الحزمة#

استبدل معالجات التثبيت وإعادة التوجيه وWebhooks لديك بالمستقبِل (أو وحدة Nuxt). احتفظ بمساراتك: في dukkan.app.toml اضبط install_path وcallback_path وwebhook_path على ما يعرفه التجار والمنصّة أصلاً، فتبقى روابط إعادة التوجيه المسجّلة وروابط نقاط النهاية صالحة.

يتغيّر سلوكان في هذه المرحلة ويستحقان ملاحظة إصدار:

  • تصبح Cookie حالة OAuth هي Cookie الحزمة الموقّعة ببادئة __Host-؛ والتاجر الذي في منتصف الموافقة وقت النشر يعيد التثبيت من البداية.
  • تجيب حالات الرفض بأجسام JSON الصغيرة للمستقبِل ({ "error": "bad_signature" } وأمثالها). لا تقرأ المنصّة سوى الحالة، فتتصرّف إعادة المحاولة كما كانت.

٥. احذف الحلول الالتفافية#

عندما يحمل كل صف حي install_id، أزل الشيفرة التي كانت تربط المتاجر من الأغلفة أو تجرّب أسرار الصفوف الشقيقة أو تسوّي سباقات التجديد. راقب عدّاداً في نقطة الصحة حتى يصل إلى الصفر قبل ذلك.

قائمة التحقق#

  • لا تُسجَّل الرموز في السجلات أبداً؛ تمرّ كل رسالة خطأ في الحزمة عبر redactSecrets، وينبغي أن تمرّ سجلاتك أنت أيضاً.
  • عملية واحدة فقط تجدّد التثبيت في أي وقت (withRefreshLock).
  • ترسل كل كتابة Idempotency-Key؛ وتُحفظ المفاتيح التاريخية حيث قد تعبر إعادةُ المحاولة الانتقالَ.
  • تُزال التسليمات المكررة على id الموقّع، لا على ترويسة التسليم.
  • يحذف app.uninstalled ما تتطلبه معالجة البيانات.

الخطوات التالية#