البدء السريع

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

كيف يعمل التكامل

نظامان يتخاطبان في الاتجاهين: منصتك تستدعي Quroosh Partner API، وقروش يرسل Webhooks إلى endpoint الاستقبال في منصتك. بعض الخطوات عندك، وبعضها عند التاجر في لوحة تحكم قروش:

#أينماذا يحدث
١لوحة تحكم قروش (التاجر)التاجر يفعّل التكامل ويحصل على مفتاح API وسر موقِّع — يظهران مرة واحدة — ويسلّمهما لك.
٢منصتكتخزّن القيمتين، ثم تستدعي GET /connection للتأكد أن الاتصال يعمل.
٣منصتكترسل حدث BranchSynced لكل فرع من فروعك.
٤لوحة تحكم قروش (التاجر)التاجر يربط كل فرع من فروعك بفرع في قروش. المبيعات تُرفض حتى يكتمل هذا الربط.
٥منصتكترسل CustomerSynced لعملائك، ثم InvoiceEarned مع كل عملية بيع. النقاط تُحتسب تلقائياً.
٦قروش ← منصتكقروش يرسل Webhooks (تغييرات قواعد الولاء، حالة التكامل) إلى endpoint الاستقبال لديك، موقَّعة بنفس السر. الـ header X-TenantId يخبرك لأي تاجر يعود التسليم.

١. الحصول على مفاتيح الربط (لوحة تحكم قروش)

مفاتيح الربط يصدرها التاجر، وليس دعم قروش. في لوحة تحكم التاجر، يفتح التاجر صفحة التكاملات مع الشركاء، يختار منصتك وأي بيئة من بيئاتك يستهدفها التكامل (اختبارية أو إنتاجية — رابط الاستقبال لكل بيئة يُسجَّل لدى قروش عند تأسيس الشراكة)، يُدخل معرّف حساب التاجر على منصتك، ثم يفعّل. تعرض اللوحة عندها قيمتين مرة واحدة فقط:

القيمةشكلهاتستخدمها في
مفتاح APIqpk_9f2a…يُرسل في header X-Api-Key مع كل طلب. المفتاح يحدد التاجر — لا ترسل معرّف التاجر في جسم الطلب أبداً.
السر الموقِّعنص عشوائيحساب header X-Signature على POST /events، والتحقق من توقيع Webhooks التي يرسلها قروش إليك.
تظهر مرة واحدة — خزّنها الآن

قروش يحتفظ بالتجزئة (hash) فقط ولا يعرض القيم الخام مرة أخرى. خزّن القيمتين في مخزن أسرار على خادمك. إذا فُقدت قيمة، يجدّدها التاجر من نفس الصفحة: التجديد يصدر قيمة جديدة بينما تبقى القديمة صالحة حتى تُلغى صراحةً، فالانتقال يتم دون أي انقطاع.

بيئة الاختبار والإنتاج منفصلتان: التاجر يفعّل التكامل في لوحة كل بيئة على حدة، وكل بيئة تصدر مفاتيحها الخاصة. مفتاح بيئة الاختبار يعمل فقط مع api-staging.quroosh.io.

٢. اختبار الاتصال

GET /connection هو اختبار الاتصال. استجابة ناجحة تثبت أن المفتاح وتحديد التاجر وحالة التكامل تعمل من البداية إلى النهاية، وتعيد اسم متجر التاجر — اعرضه بصيغة «متصل بـ …» ليُكتشف فوراً أي مفتاح لُصق لحساب خاطئ. طلبات GET لا تحتاج توقيعاً — المفتاح وحده يكفي:

curl https://api-staging.quroosh.io/api/partner/v1/connection \
  -H "X-Api-Key: qpk_XXXXXXXXXXXX"

200 OK:

{
  "connected": true,
  "storeId": "0195d0a0-0000-7000-8000-0000000000aa",
  "storeName": "Karam Coffee",
  "platform": "Kosoor",
  "environment": "Production"
}

environment هي البيئة التي يسلّم قروش الـ Webhooks إليها على جانبك — إذا ظهرت Testing وأنت تتوقع الإنتاج، فقد اختار التاجر البيئة الخاطئة عند التفعيل.

إذا فشل الطلب، كل الأخطاء تستخدم نفس البنية ({ "code", "message", "correlationId" }):

الحالةالرمزالحل
401MISSING_API_KEYheader X-Api-Key غير موجود.
401INVALID_API_KEYالمفتاح غير معروف أو ملغى — تحقق من القيمة، أو اطلب من التاجر التجديد.
401INTEGRATION_NOT_CONNECTEDالتاجر لم يفعّل التكامل بعد (الخطوة ١).
503INTEGRATION_PAUSEDالتاجر أوقف التكامل مؤقتاً. المفاتيح ما زالت صالحة — أعد المحاولة لاحقاً.

٣. مزامنة فروعك

كل ما ترسله إلى قروش يمر عبر endpoint واحد:POST /api/partner/v1/events. كل طلب هومظروف حدث موقَّع بسرّك. اكتب دالة الإرسال مرة واحدة وأعد استخدامها لكل أنواع الأحداث في هذا الدليل:

using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

async Task<EventAckResponse?> SendEventAsync(
    HttpClient client, string apiKey, string signingSecret,
    string eventType, string entityType, string entityId, object payload)
{
    // Serialize once so the bytes you sign are the exact bytes you send.
    var rawBody = JsonSerializer.SerializeToUtf8Bytes(new
    {
        eventId = Guid.CreateVersion7(),   // dedup key — one per business event
        occurredAt = DateTimeOffset.UtcNow,
        entityType,
        entityId,
        sourceSystem = "Kosoor",           // your platform identifier
        schemaVersion = 1,
        correlationId = Guid.CreateVersion7(),
        eventType,
        payload
    });

    // X-Signature: t=<unix_ts>,v1=<hex> — HMAC-SHA256 over "{timestamp}." + raw body.
    var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    using var hmac = IncrementalHash.CreateHMAC(
        HashAlgorithmName.SHA256,
        Encoding.UTF8.GetBytes(signingSecret));
    hmac.AppendData(Encoding.UTF8.GetBytes($"{timestamp}."));
    hmac.AppendData(rawBody);
    var signature = $"t={timestamp},v1={Convert.ToHexStringLower(hmac.GetHashAndReset())}";

    using var content = new ByteArrayContent(rawBody);
    content.Headers.ContentType = new("application/json");
    using var request = new HttpRequestMessage(HttpMethod.Post, "api/partner/v1/events")
    {
        Content = content
    };
    request.Headers.Add("X-Api-Key", apiKey);
    request.Headers.Add("X-Signature", signature);

    using var response = await client.SendAsync(request);
    return await response.Content.ReadFromJsonAsync<EventAckResponse>();
}
وقّع البايتات الخام

HMAC يُحسب على تسلسل البايتات الذي سيستقبله قروش تماماً. إعادة تسلسل الـ JSON بين التوقيع والإرسال (فرق مسافات أو ترتيب مفاتيح) تُفشل التحقق — احتفظ بمصفوفة byte[] الخام وأعد استخدامها للتوقيع والإرسال معاً.

الآن عرّف قروش على كل فرع من فروعك بحدث BranchSynced:

var branchId = Guid.Parse("7c9e6679-7425-40de-963d-0d5f8b1a2c11"); // your branch's ID

var ack = await SendEventAsync(client, apiKey, signingSecret,
    eventType: "BranchSynced",
    entityType: "Branch",
    entityId: branchId.ToString(),
    payload: new
    {
        externalBranchId = branchId,
        name = "Olaya Branch",
        isActive = true
    });

200 OK:

{
  "accepted": true,
  "eventId": "0195d1c7-6e40-7f8e-9a2b-000000000001",
  "alreadyProcessed": false
}

accepted يعني أن الحدث سُجِّل. إعادة إرسال نفسeventId تُعيد الاستجابة المخزّنة معalreadyProcessed: true — المعالجة لا تحدث مرتين أبداً، فإعادة المحاولة آمنة دائماً.

٤. التاجر يربط الفروع (لوحة تحكم قروش)

كل فرع تزامنه يصل إلى قروش بحالة غير مربوط. قروش لا يُظهر أي تنبيه تلقائي — التاجر يفتح التكاملات مع الشركاء ← ربط الفروع، يختار الفرع المطابق في قروش لكل فرع من فروعك، ثم يؤكد. الربط يُعدَّل هناك فقط، فلا يمكن أن يختلف الطرفان على أي فرع يقابل أي فرع.

وتستطيع متابعة الحالة من جهتك في أي وقت:

curl https://api-staging.quroosh.io/api/partner/v1/branch-mappings \
  -H "X-Api-Key: qpk_XXXXXXXXXXXX"
{
  "mappings": [
    {
      "externalBranchId": "7c9e6679-7425-40de-963d-0d5f8b1a2c11",
      "externalBranchName": "Olaya Branch",
      "status": "Mapped",
      "mappedStoreId": "0195d0a0-0000-7000-8000-0000000000aa",
      "mappedStoreName": "Karam Coffee",
      "mappedBranchId": "0195d0a0-0000-7000-8000-000000000001",
      "mappedBranchName": "Olaya"
    }
  ]
}
المبيعات تُرفض حتى يُربط الفرع

حدث InvoiceEarned لفرع غير معروف يفشل برمزBRANCH_MAPPING_NOT_FOUND، ولفرع مُزامَن لكن غير مؤكد برمز BRANCH_MAPPING_UNCONFIRMED. الأحداث الفاشلة لا تُخزَّن أبداً — بمجرد أن يؤكد التاجر الربط، أعد إرسال نفس الحدث (بنفس eventId) وسينجح.

٥. مزامنة عميل

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

var ack = await SendEventAsync(client, apiKey, signingSecret,
    eventType: "CustomerSynced",
    entityType: "Customer",
    entityId: "cust_012345",
    payload: new
    {
        externalCustomerId = "cust_012345",
        phone = "+966501234567",
        firstName = "Sara",
        lastName = "Alqahtani"
    });

أرسل الحدث عند إنشاء العميل وعند تعديله. رقم الجوال هو ما يربط عميلك بحسابه في برنامج الولاء.

٦. إرسال أول عملية بيع

اللحظة التي بنيت كل ما سبق لأجلها — عميل يكسب نقاطاً على فاتورة بقيمة ١٠٤٫٣٥ ريال:

var invoiceId = Guid.CreateVersion7();

var ack = await SendEventAsync(client, apiKey, signingSecret,
    eventType: "InvoiceEarned",
    entityType: "Invoice",
    entityId: invoiceId.ToString(),
    payload: new
    {
        externalInvoiceId = invoiceId,
        externalCustomerId = "cust_012345",
        externalBranchId = branchId,
        subtotalAfterDiscountBeforeVat = 104.35m,
        earnType = "Points",
        stampCardId = (Guid?)null,
        channel = "Pos"
    });

Console.WriteLine($"accepted={ack!.Accepted} alreadyProcessed={ack.AlreadyProcessed}");

accepted: true ورصيد العميل يتحرك. من هنا فصاعداً، أرسلInvoiceEarned مع كل عملية بيع مكتملة.

٧. ماذا تبني بعد ذلك

  • الاستعلام عند الكاشيرGET /customers/lookup?phone=… يعيد رصيد العميل ومستواه ومكافآته القابلة للاستبدال وتقدّم الأختام لحظياً عند نقطة البيع. راجع الاستعلام عن العملاء.
  • المنتجات والكوبونات — أرسل ProductSyncedوCouponSynced بنفس طريقة مزامنة الفروع، إضافة إلىCouponUsed / CouponUsageCancelled عند الاستخدام. بعدها يربط التاجر مكافآت قروش بمنتجاتك من محرر المكافآت. راجع أنواع الأحداث لكل البنى.
  • استقبال Webhooks — قروش يرسلLoyaltyRuleChanged وIntegrationStatusChangedإلى endpoint الاستقبال المسجَّل لديك، موقَّعة بنفس منظومةX-Signature؛ وجّه كل تسليم حسب الـ headerX-TenantId. راجع Webhooksلكود التحقق وجدول إعادة المحاولة.

قائمة التحقق قبل الإطلاق

  • GET /connection يعيد اسم متجر التاجر على بيئة الاختبار.
  • كل فرع يظهر بحالة Mapped في GET /branch-mappings.
  • العملاء يتزامنون عند الإنشاء وعند التعديل.
  • حدث InvoiceEarned يُقبل، وإعادة إرسال نفس eventId تعيد alreadyProcessed: true.
  • رابط الاستقبال عندك يتحقق من X-Signature ويستجيب بـ 2xx.
  • التاجر يفعّل التكامل على بيئة الإنتاج وتستبدل مفاتيح الإنتاج بمفاتيح الاختبار.

تابع القراءة

  • المصادقة — منظومة التوقيع بالتفصيل، تجديد المفاتيح، رموز الأخطاء.
  • أنواع الأحداث — الكتالوج الكامل لـ ١٦ حدثاً وبنى المحتوى.
  • الأخطاء — كل رموز الأخطاء، بما فيها رموز معالجة الأحداث.
  • مرجع API — بنى الطلبات والاستجابات مع ساحة تجربة مباشرة.