أنواع الأحداث

كل حدث ترسله إلى POST /api/partner/v1/events يستخدم نفس المظروف؛ يتغير فقط نص eventType ومحتوى payload. هذه الصفحة تسرد كل أنواع الأحداث الـ ١٦ مع مثال body لكل نوع — انسخ، عدّل، وقّع، أرسل.

التوفّر

كل حدث موسوم بحالته الحالية:

  • متاحمُعالَج اليوم — أرسله ويأخذ مفعوله.
  • مُخططمقبول على مستوى الاتصال لكن الـ handler لم يُوصَل بعد، فيُرجِع503 قابلاً لإعادة المحاولة. طوّر مقابله، لكن توقّع الرفض حتى يُطلَق.
  • صادرقروش يرسل هذا الحدث إليك (راجع Webhooks). إرساله إلى /api/partner/v1/events يُرجِع400 WRONG_EVENT_DIRECTION. شكل الـ payload موثّق هنا لتعرف ما ستستقبله.

المظروف

يغلّف كل حدث. التاجر يُستخرَج من مفتاح API، ليس من الـ body.

{
  "eventId":       "<Guid v7 — فريد لكل حدث، يُستخدم لمنع التكرار>",
  "occurredAt":    "<ISO-8601 UTC — وقت حدوث الحدث في نظامك>",
  "entityType":    "Invoice | Redemption | LoyaltyRule | Branch | Customer | Product | Coupon | Integration",
  "entityId":      "<string — معرّف الكيان المتأثر في نظامك>",
  "sourceSystem":  "Kosoor | Quroosh",
  "schemaVersion": 1,
  "correlationId": "<Guid — اختياري، يربط الأحداث بين الأنظمة>",
  "eventType":     "<إحدى القيم الـ ١٦ أدناه>",
  "payload":       { /* object يعتمد على eventType، راجع كل قسم أدناه */ }
}
نفس المظروف، منع التكرار على eventId

إعادة المحاولة يجب أن تحمل نفس eventId. قروش يحتفظ بالاستجابة لمدة ٢٤ ساعة ويعيدها — الـ handler التجاري لا يعمل مرتين.

أحداث المعاملات

InvoiceEarned متاح

العميل كسب نقاطاً أو ختماً على عملية شراء.

{
  "eventType": "InvoiceEarned",
  "payload": {
    "externalInvoiceId":              "0195d1c7-8000-7000-8000-000000000123",
    "externalCustomerId":             "cust_012345",
    "externalBranchId":               "0195d0a0-0000-7000-8000-000000000001",
    "subtotalAfterDiscountBeforeVat": 104.35,
    "earnType":                       "Points",
    "stampCardId":                    null,
    "channel":                        "Pos"
  }
}
  • earnType: Points أو Stamps. عدد النقاط أو الأختام يُحسَب على الخادم من قواعد التاجر النشطة — لا يُرسَل من الشريك.
  • stampCardId: مطلوب فقط عندما يكون earnType = "Stamps". Null للنقاط.
  • channel: Pos أو Kiosk.
  • منفعة واحدة لكل فاتورة: أي externalInvoiceId سبق احتسابه يُرفض بـ PARTNER_INVOICE_ALREADY_EARNED حتى مع eventId جديد.
  • بطاقة أختام PerVisit ما زالت ضمن فترة الانتظار تُرجِع 200 بدون أختام — قبول وليس خطأ.

InvoiceRefunded مُخطط

تم إرجاع فاتورة. قروش يعكس الكسب الأصلي.

{
  "eventType": "InvoiceRefunded",
  "payload": {
    "externalInvoiceId":         "0195d1c7-8000-7000-8000-000000000124",
    "externalOriginalInvoiceId": "0195d1c7-8000-7000-8000-000000000123",
    "refundedAmount":            104.35,
    "isOverride":                false
  }
}
  • externalInvoiceId: معرّف فاتورة الإرجاع نفسها.
  • externalOriginalInvoiceId: الفاتورة الأصلية التي تُرجَع.
  • isOverride: true فقط إذا فرض المدير الإرجاع رغم منع التحقق.

RefundReversalCompleted صادر

متابعة قروش لـ InvoiceRefunded — يُبلغ بما حدث فعلياً بعد تطبيق السياسة. أنت تستقبل هذا الحدث؛ ولا ترسله أبداً.

{
  "eventType": "RefundReversalCompleted",
  "payload": {
    "externalInvoiceId": "0195d1c7-8000-7000-8000-000000000124",
    "status":            "Reversed",
    "pointsReversed":    120,
    "stampsReversed":    null,
    "newBalance":        380
  }
}
  • status: Reversed (نظيف)، OverrideApplied، أو NegativeBalanceByPolicy.
  • يُسلَّم كـ webhook صادر بمجرد إطلاق عكس الإرجاع؛ لم يُطلَق بعد.

استخدام الكوبونات

CouponUsed متاح

تم استخدام كوبون على فاتورة.

{
  "eventType": "CouponUsed",
  "payload": {
    "externalCouponId":   "0195d1c7-9000-7000-8000-000000000010",
    "externalCustomerId": "cust_012345",
    "externalInvoiceId":  "0195d1c7-8000-7000-8000-000000000123"
  }
}

CouponUsageCancelled متاح

يعكس CouponUsed سابقاً.

{
  "eventType": "CouponUsageCancelled",
  "payload": {
    "externalCouponId":  "0195d1c7-9000-7000-8000-000000000010",
    "externalInvoiceId": "0195d1c7-8000-7000-8000-000000000123"
  }
}

أحداث المزامنة (كيان واحد)

BranchSynced متاح

{
  "eventType": "BranchSynced",
  "payload": {
    "externalBranchId": "0195d0a0-0000-7000-8000-000000000001",
    "name":             "الرياض — العليا",
    "isActive":         true
  }
}

CustomerSynced متاح

{
  "eventType": "CustomerSynced",
  "payload": {
    "externalCustomerId": "cust_012345",
    "phone":              "+9665XXXXXXXX",
    "firstName":          "أحمد",
    "lastName":           "العتيبي"
  }
}

firstName و lastName اختياريان.

ProductSynced متاح

{
  "eventType": "ProductSynced",
  "payload": {
    "externalProductId": "sku_00042",
    "name":              "كورتادو — ١٢ أونصة",
    "price":             18.00,
    "isActive":          true
  }
}

أرسل isActive: false عند حذف المنتج أو تعطيله — أي مكافأة قروش مربوطة به تصبح غير قابلة للربط وتُرفض عمليات الاستبدال التي تعتمد عليها. عدم إرسال isActive يعتبرهtrue افتراضياً.

CouponSynced متاح

{
  "eventType": "CouponSynced",
  "payload": {
    "externalCouponId":       "0195d1c7-9000-7000-8000-000000000010",
    "name":                   "رمضان ٢٠٪",
    "discountType":           "Percentage",
    "discountValue":          20.00,
    "isLoyaltyProgramCoupon": false
  }
}
  • discountType: FixedAmount (مبلغ بالريال) أو Percentage (نسبة من الفاتورة).
  • isLoyaltyProgramCoupon: true لكوبون أنشأه قروش لاستبدال ضمن برنامج الولاء؛ false لكوبونات التاجر الخاصة. القيمة الافتراضية false.

أحداث المزامنة (دفعات)

للاستيراد المبدئي الكبير أو المطابقة الدورية. الـ payload يغلّف نفس شكل الكيان الواحد في items.

BranchesSyncedBatch مُخطط

{
  "eventType": "BranchesSyncedBatch",
  "payload": {
    "items": [
      { "externalBranchId": "0195d0a0-...-001", "name": "الرياض — العليا", "isActive": true },
      { "externalBranchId": "0195d0a0-...-002", "name": "جدة — الكورنيش",  "isActive": true }
    ]
  }
}

CustomersSyncedBatch مُخطط

{
  "eventType": "CustomersSyncedBatch",
  "payload": {
    "items": [
      { "externalCustomerId": "cust_012345", "phone": "+9665XXXXXXX1" },
      { "externalCustomerId": "cust_012346", "phone": "+9665XXXXXXX2", "firstName": "سارة" }
    ]
  }
}

ProductsSyncedBatch مُخطط

{
  "eventType": "ProductsSyncedBatch",
  "payload": {
    "items": [
      { "externalProductId": "sku_00042", "name": "كورتادو — ١٢ أونصة", "price": 18.00, "isActive": true },
      { "externalProductId": "sku_00043", "name": "لاتيه — ١٦ أونصة",   "price": 22.00, "isActive": true }
    ]
  }
}

CouponsSyncedBatch مُخطط

{
  "eventType": "CouponsSyncedBatch",
  "payload": {
    "items": [
      { "externalCouponId": "0195...010", "name": "رمضان ٢٠٪",  "discountType": "Percentage",  "discountValue": 20.00 },
      { "externalCouponId": "0195...011", "name": "أول زيارة",  "discountType": "FixedAmount", "discountValue": 5.00 }
    ]
  }
}

تغييرات القواعد

LoyaltyRuleChanged صادر

التاجر غيّر طريقة الكسب. قروش يرسل هذا إلى الـ webhook الخاص بك لتحدّث قواعدك المخزّنة؛ ولا ترسله أبداً. الـ payload الداخلي يتبدل حسب ruleType. طبّق أي payload فقط عندما يكونruleVersion فيه أكبر من الإصدار الذي خزّنته آخر مرة — فالأحداث ليس لها ترتيب عام.

معدل الكسب (ruleType = "EarnRate")

{
  "eventType": "LoyaltyRuleChanged",
  "payload": {
    "externalStoreId": "0195d0a0-0000-7000-8000-000000000000",
    "ruleType":        "EarnRate",
    "ruleVersion":     3,
    "effectiveFrom":   "2026-07-15T00:00:00Z",
    "payload": {
      "methodType":      "OnPurchase",
      "pointCostAmount": 1.00,
      "pointCostPoints": 1,
      "pointsPerVisit":  null,
      "minimumPurchase": 20.00,
      "isActive":        true
    }
  }
}
  • methodType: OnPurchase (كسب لكل ريال) أو OnVisit (كسب ثابت لكل زيارة).
  • مع OnPurchase: عيّن pointCostAmount و pointCostPoints؛ اترك pointsPerVisit null.
  • مع OnVisit: عيّن pointsPerVisit؛ اترك حقلَي التكلفة null.
  • FreeEntry ليست طريقة كسب مقبولة من الشركاء — قروش يرفضها.

قاعدة الأختام (ruleType = "StampRule")

{
  "eventType": "LoyaltyRuleChanged",
  "payload": {
    "externalStoreId": "0195d0a0-0000-7000-8000-000000000000",
    "ruleType":        "StampRule",
    "ruleVersion":     2,
    "effectiveFrom":   "2026-07-15T00:00:00Z",
    "payload": {
      "stampCardId":       "0195d1c7-a000-7000-8000-000000000100",
      "name":              "بطاقة الأختام — قهوة",
      "mode":              "PerAmount",
      "minInvoiceAmount":  15.00,
      "amountPerStamp":    15.00,
      "cooldownDays":      null,
      "isActive":          true
    }
  }
}
  • mode: PerVisit (ختم لكل زيارة مؤهلة) أو PerAmount (ختم لكل مبلغ ريالات محدد).
  • cooldownDays: انتظار اختياري بين الأختام لنفس العميل.

دورة الحياة

RedemptionExpiredOrCancelled صادر

استرداد معلّق سقط بدون إتمام. أنت تستقبل هذا من قروش؛ ولا ترسله أبداً.

{
  "eventType": "RedemptionExpiredOrCancelled",
  "payload": {
    "redemptionId":      "0195d1c7-b000-7000-8000-000000000500",
    "externalInvoiceId": null,
    "reason":            "Expired"
  }
}
  • reason: Expired (انتهى الوقت) أو Cancelled (الموظف ألغى).
  • externalInvoiceId: يُعيَّن إذا كان الاسترداد قد ارتبط بفاتورة؛ وإلا null.

IntegrationStatusChanged صادر

التاجر فعّل أو أوقف أو فصل التكامل. قروش يرسل هذا إلى الـ webhook الخاص بك.

{
  "eventType": "IntegrationStatusChanged",
  "payload": {
    "status": "Enabled"
  }
}
  • status: Enabled، Disabled، أو Disconnected.
  • توقّف عن إرسال الأحداث بعد Disabled أو Disconnected.

كل السكيمات الـ ١٦ متوفرة أيضاً في مرجع APIضمن سكيما جسم الطلب — استخدم أي عرض تفضّله.