الانتقال إلى المحتوى الرئيسي
كل المقالات
تقني9 دقائق قراءة

التجار الفرعيون عبر API: الاستخلاص لفائدة تاجر فرعي وتتبّع محفظته بالويب هوك

التجار الفرعيون عبر API: أنشئوا جلسة دفع يُقيَّد مبلغها في محفظة تاجر فرعي عبر config.settlement، وتابعوا أحداث المحفظة والدفعات، وعالجوا الأخطاء.

التجار الفرعيون عبر API تُقيَّد لهم المبالغ بجلسة دفع عادية: أنشئوها كما في أي استخلاص، وأضيفوا داخل config الكائن settlement الذي يحدّد المحفظة التي يُقيَّد لها المبلغ — إما بـwalletId أو بـsubMerchantReference الخاص بالتاجر الفرعي. يدفع المشتري في صفحة الدفع المستضافة، ويتابع نظامكم الباقي عبر ويب هوك موقَّع: wallet.activated وwallet.rejected للتدقيق، وpayout.completed وpayout.failed للدفعات، وsubmerchant.near_cap لسقوف الرصيد. يقدّم هذا الدليل الطلب، والأحداث، والتصريح بنقطة الاستقبال، والأخطاء المرتقبة، وما يتم في بوابة التاجر وما يتم عبر API، والمطابقة حسب التاجر الفرعي.

التجار الفرعيون عبر API: المحفظة وwalletId وsubMerchantReference

ChariPay منصة دفع مغربية، مزوّد خدمات دفع (PSP) تشغّله Chari Money، مؤسسة أداء مرخَّصة من بنك المغرب. وفضاء التوزيع فيها موجَّه إلى المجمِّعين وشبكات الامتياز وشبكات الوكلاء. والتاجر الفرعي كيان تنشئونه في فضاء التوزيع الخاص بكم، كبائع أو نقطة بيع مثلًا؛ يُدقَّق كأي تاجر — الوثائق والسجل التجاري — قبل الاستخلاص في الإنتاج، وله محفظته الخاصة.

ومن جهة API، تكفي أربعة مفاهيم:

المصطلحمعناهالصيغة
التاجر الفرعيكيان يُنشأ في فضاء التوزيع الخاص بكم، يُدقَّق كأي تاجريُصرَّح به من بوابة التاجر أو عبر API
المحفظةالمحفظة الخاصة بالتاجر الفرعي، تُقيَّد لها مبالغ الجلسات التي تحدّدهامحفظة لكل تاجر فرعي
walletIdمعرّف محفظة التاجر الفرعي التي يُقيَّد لها المبلغUUID، مثلًا 3fa85f64-5717-4562-b3fc-2c963f66afa6
subMerchantReferenceمرجع التاجر الفرعي الذي يُقيَّد له المبلغسلسلة نصية، مثلًا subm_9Zt2Bc

ويوضع المعرّفان في config.settlement، وهو كائن اختياري في الجلسة: أرسلوا أحدهما، أيّهما تحتفظ به قاعدة بياناتكم أصلًا. ولا يُستعمل هذا الكائن إلا لتحديد محفظة تاجر فرعي يُقيَّد لها المبلغ.

ويتم التصريح بالتاجر الفرعي من بوابة التاجر أو عبر API؛ ولا يفصّل العقد العمومي لـAPI هذا النداء، الذي يُعَدّ مع فريق الإدماج. أما العرض فمعروض في صفحة التوزيع والتجار الفرعيون، والنموذج التجاري لمنصة متعددة البائعين في مدفوعات منصات السوق الإلكترونية.

إنشاء جلسة يُقيَّد مبلغها لتاجر فرعي

الطلب هو طلب جلسة دفع عادية، مع كتلة settlement إضافية داخل config:

bash
curl -X POST 'https://api-psp.charipay.ma/v1/payment-sessions' \
  -H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-2026-0421-session' \
  -d '{
    "amount": 250.00,
    "orderId": "ORD-2026-0421",
    "externalId": "order-2026-0421",
    "config": {
      "customer": {
        "email": "amine.bennani@example.com",
        "firstName": "Amine",
        "lastName": "Bennani",
        "phone": "+212600000000"
      },
      "urls": {
        "accept": "https://votre-boutique.ma/paiement/succes",
        "decline": "https://votre-boutique.ma/paiement/echec",
        "notification": "https://votre-boutique.ma/webhooks/charipay"
      },
      "settlement": {
        "subMerchantReference": "subm_9Zt2Bc"
      }
    },
    "metadata": { "cartId": "c_987", "source": "web" }
  }'

ويتضمن الرد (201، أو 200 عند إعادة الإرسال بنفس externalId) هذه الحقول الخمسة ولا شيء غيرها:

json
{
  "sessionId": "ps_5Kd0Rn",
  "sessionToken": "st_…",
  "verifyKey": "vk_5Kd0Rn",
  "expiresAt": "2026-10-05T10:15:00Z",
  "checkoutUrl": "https://…/checkout/ps_5Kd0Rn"
}

أعيدوا توجيه المشتري إلى checkoutUrl: فيدفع بالبطاقة مع ‎3-D Secure في الصفحة المستضافة. ولا يعيد الرد حالةً ولا مبلغًا ولا تاجرًا فرعيًا: تُقرأ الحالة والمبلغ عبر GET /v1/payment-sessions/{sessionId}، وعلى قاعدة بياناتكم أن تحتفظ بالربط بين sessionId والتاجر الفرعي. ولكل جلسة مدة حياة محدودة — 72 ساعة افتراضيًا، وexpiresAt لتقصيرها — ولا تُستهلك إلا مرة واحدة. أكّدوا الطلبية عند استلام الويب هوك، لا عند إعادة التوجيه وحدها. والحقول والردود مفصّلة في وحدة «جلسات الدفع» في توثيق API.

ويمكنكم كتابة هذا النداء الأول اليوم: ابدؤوا في وضع الاختبار. ثلاثة حقول — الاسم، والبريد الإلكتروني المهني، والمؤسسة — ثم رابط تفعيل يصلكم بالبريد لاختيار كلمة المرور، ثم بوابة التاجر حيث تنشئون بأنفسكم مفتاح الاختبار بعد إعادة مصادقة؛ ولا يُعرض المفتاح كاملًا إلا مرة واحدة. ادفعوا ببطاقة الاختبار ‎4918 9141 0719 5005، CVV 123، وأي تاريخ انتهاء مستقبلي، ورمز ‎3-D Secure 555. هكذا تنشئون جلسات دفع فورًا؛ أما تركيب التوزيع — ومن ثَمّ التجار الفرعيون الذين يحدّدهم config.settlement — فيُحضَّر مع فريق الإدماج.

أحداث مسار التاجر الفرعي

خمسة أحداث ويب هوك تخصّ التوزيع. يدلّ اسم كل منها على ما يُخطر به؛ أما ردّ الفعل فلكم:

الحدثما يُخطر بهردّ الفعل المقترح
wallet.activatedتفعيل محفظة تاجر فرعي بعد التدقيقجعل التاجر الفرعي «نشطًا» في قاعدتكم وفتح مبيعاته
wallet.rejectedرفض محفظة تاجر فرعي عند التدقيقإيقاف مبيعاته، وإخباره، ومراجعة ملفه
payout.completedتنفيذ دفعةتسجيل الدفعة منفَّذة في محاسبتكم
payout.failedفشل دفعةتنبيه الفريق المالي والتحقيق قبل أي طلب جديد
submerchant.near_capاقتراب تاجر فرعي من سقف رصيدهإخطار مسؤول نقطة البيع أو الفريق المالي

أما الأداءات نفسها فتصل عبر payment.succeeded. وجّهوا المعالجة دائمًا حسب الترويسة Chari-Event-Type، واقرؤوا معرّف الحدث في Chari-Event-Id. أزيلوا التكرار على Chari-Event-Id لا على Chari-Webhook-Id — فهذا الأخير يحدّد عملية التسليم لا الحدث. وتحقّقوا من التوقيع X-CHARI-SIGNATURE مع X-CHARI-TIMESTAMP قبل أي معالجة، ولا تبنوا شيئًا على حقول الجسم غير metadata الخاص بكم. والفهرس وقواعد التسليم في وحدة «الويب هوك» في توثيق API.

التصريح بنقطة الاستقبال التي تتلقى هذه الأحداث

يُصرَّح بنقطة استقبال الويب هوك عبر POST /api/v1/partner/webhooks/endpoints، بمفتاح البيئة المقصودة. اشتركوا في لائحة صريحة من الأحداث: فاللائحة الفارغة تعني الاشتراك في كل الأحداث، الحالية والمستقبلية.

bash
curl -X POST 'https://api-psp.charipay.ma/api/v1/partner/webhooks/endpoints' \
  -H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://votre-boutique.ma/webhooks/charipay",
    "description": "Paiements boutique",
    "enabledEvents": [
      "payment.succeeded",
      "wallet.activated",
      "wallet.rejected",
      "payout.completed",
      "payout.failed",
      "submerchant.near_cap"
    ]
  }'

ثلاث قواعد ينبغي معرفتها:

  • يجب أن يكون url بـHTTPS عمومي على المنفذ 443: فـlocalhost والعناوين الخاصة وHTTP غير المشفّر مرفوضة منذ التسجيل — وفي التطوير مرّوا عبر نفق؛
  • يحمل الرد signingSecret (whsec_…) مرة واحدة فقط، عند الإنشاء أو التدوير: احفظوه فورًا في خزنة؛
  • تنتمي نقطة الاستقبال إلى بيئة واحدة، والأسرار تختلف بين السندبوكس والإنتاج.

ويمكن إجراء التصريح نفسه من بوابة التاجر. وللضبط محليًا — النفق، والحدث التجريبي، وسجل التسليمات — انظروا اختبار الويب هوك محليًا.

الأخطاء المرتقبة

تشترك كل أخطاء API /v1 في الغلاف نفسه:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "amount: must be greater than 0"
  },
  "correlationId": "b0c1e2d3-4f56-7890-abcd-ef0123456789"
}

اختبروا code لا message، وسجّلوا correlationId. والرموز المفيدة في هذا المسار:

الحالةالرمزمعناهما العمل
400VALIDATION_ERRORالطلب غير سليم أو لم يجتز التحققتصحيح الطلب قبل إعادة إرساله، وعدم إعادته كما هو
401UNAUTHORIZEDمفتاح API غائب أو غير صالحالتحقق من الترويسة X-CHARI-PAY-API-KEY ومن بيئة المفتاح
403PRODUCTION_ACCESS_NOT_ENABLEDاستعمال مفتاح chari_sk_live_… قبل تفعيل الإنتاجالبقاء في وضع الاختبار إلى حين التفعيل
409IDEMPOTENCY_CONFLICTنفس Idempotency-Key بجسم مختلفتوليد مفتاح جديد لكل طلب جديد
422WALLET_NOT_ACTIVEطلب صالح لكن قاعدة عمل تمنعهعدم توجيه المشتري؛ التسجيل ومراجعة فريق الإدماج إن استمر الرمز

ويقدّم العقد WALLET_NOT_ACTIVE مثالًا عامًّا على رمز 422 في معظم نقاطه: فلا تنسبوا إليه سببًا أدقّ مما يقوله اسمه. وتذكّروا أيضًا أن إعادة الإرسال بـexternalId موجود تعيد 200 OK بدل 201 Created: كلاهما نجاح، فاختبروا الحالة.

ما يتم في بوابة التاجر وما يتم عبر API

العمليةأين تتمما يراه نظامكم
التصريح بتاجر فرعي، واحدًا واحدًا أو دفعةً واحدةبوابة التاجر أو APIالتاجر الفرعي ومحفظته الخاصة
تدقيق التاجر الفرعيمدمج: الوثائق والسجل التجاريwallet.activated أو wallet.rejected
الاستخلاص لفائدة تاجر فرعيAPI: جلسة مع config.settlementpayment.succeeded مع metadata الخاص بكم
تلقّي الأحداثنقطة استقبال يُصرَّح بها عبر API أو من بوابة التاجرالترويستان Chari-Event-Type وChari-Event-Id
الدفعاتدفعات بموافقة مزدوجة: طالب وموافِق مختلفانpayout.completed أو payout.failed
تسقيف الرصيدسقوف رصيد تُحدَّد عند الإعدادsubmerchant.near_cap

وكل ما تبقّى — التوزيع، ووتيرة التجميع التلقائي، وتنظيم الموافِقين — يُحضَّر مع فريق الإدماج. واحفظوا الحدّ الفاصل: في ما يخص التوزيع، يغطي العقد العمومي لـAPI الجلسة مع config.settlement والأحداث؛ أما الباقي فيتبع الحوكمة المحدَّدة عند الإعداد.

مطابقة المستخلصات حسب التاجر الفرعي

لا يسمّي رد الإنشاء التاجرَ الفرعي، ولا ينبغي الاعتماد على أي حقل غير موثَّق في جسم الحدث للعثور عليه. فاجعلوا كل أداء يحمل ما يعرّفه: أضيفوا مرجع التاجر الفرعي إلى metadata — مثلًا "subMerchantReference": "subm_9Zt2Bc" بجانب cartId. يعود metadata الخاص بكم كما هو في حدث الأداء، ويحمل حدث الجلسة أيضًا orderId الخاص بكم. استرجعوا طلبيتكم بهاتين القيمتين: فـexternalId الخاص بالجلسة لا يعود في الحدث.

قاعدتان للبناء تكفيان:

  1. 1عند إنشاء الجلسة، سجّلوا في قاعدتكم السطر sessionId ← التاجر الفرعي ← الطلبية.
  2. 2عند وصول payment.succeeded، استرجعوا هذا السطر عبر metadata وعلّموا الطلبية مدفوعة.

وبعد انقطاع خادمكم، طابقوا عبر GET /v1/transactions: فالاستعلام الدوري حلّ احتياطي، لا مصدر الحقيقة. ويُعَدّ أيضًا لكل تاجر فرعي كشف شهري لا يتتبع إلا نشاطه هو.

الانتقال إلى الإنتاج مع شبكة من التجار الفرعيين

بعد التحقق من KYB — وثائق الهوية والسجل التجاري، مع تحقق بشري مزدوج — يُدرس ملفكم وتتلقون عرضًا مرقَّمًا، ثم يُفعَّل حسابكم ويُنشأ مفتاح الإنتاج. وفي الشفرة لا تغيّرون سوى المفتاح وسرّ الويب هوك: فالعناوين والحمولات ورموز الأخطاء متطابقة. وإلى أن يُفعَّل الإنتاج، يتلقى مفتاح chari_sk_live_… الرد 403 PRODUCTION_ACCESS_NOT_ENABLED. واحتفظوا بالسندبوكس بيئةً للاختبار القَبلي، بمجموعتين من متغيرات البيئة.

أما التوزيع فيُحضَّر تركيبه — التجار الفرعيون، والسقوف، والتجميع، والموافِقون — مع فريق الإدماج؛ ولا نَعِد هنا بشيء عن سلوكه في السندبوكس. ولباقي API — روابط الدفع، والاستردادات، والمعاملات — انظروا API الدفع في المغرب.

أسئلة شائعة

ما الفرق بين walletId وsubMerchantReference؟

كلاهما يحدّد وجهة الأداء في config.settlement. فـwalletId معرّف محفظة التاجر الفرعي بصيغة UUID، وsubMerchantReference مرجع التاجر الفرعي، مثلًا subm_9Zt2Bc. أرسلوا أحدهما، أيّهما تحتفظ به قاعدة بياناتكم أصلًا. والكائن settlement اختياري: لا يُستعمل إلا لتحديد محفظة تاجر فرعي. وما دام الحقلان يؤديان الغرض نفسه، فاختاروا أحدهما والتزموا به في كل نداءاتكم: ستصبح مطابقاتكم أبسط.

هل يمكن التصريح بتاجر فرعي عبر API؟

نعم: يتم التصريح من بوابة التاجر أو عبر API، واحدًا واحدًا أو دفعةً واحدة. ولا يفصّل العقد العمومي لـAPI هذا النداء؛ إذ يُعَدّ مع فريق الإدماج عند تركيب التوزيع. وفي كل الحالات يُدقَّق التاجر الفرعي كأي تاجر — الوثائق والسجل التجاري — قبل الاستخلاص في الإنتاج.

ماذا أفعل إن أعاد إنشاء الجلسة خطأ 422؟

لا تعيدوا توجيه المشتري. يعني 422 أن الطلب صالح لكن قاعدة عمل تمنعه؛ ويذكر العقد WALLET_NOT_ACTIVE مثالًا، دون أن يقول اسمه أكثر من ذلك. اقرؤوا code، وسجّلوا correlationId، وتحقّقوا في قاعدتكم من حالة التاجر الفرعي المحدَّد، وراجعوا فريق الإدماج إن استمر الرمز.

ما أحداث الويب هوك التي تُتابَع لشبكة تجار فرعيين؟

payment.succeeded للأداءات، ثم أحداث التوزيع الخمسة: wallet.activated وwallet.rejected للتدقيق، وpayout.completed وpayout.failed للدفعات، وsubmerchant.near_cap للسقوف. اشتركوا بنقطة الاستقبال في هذه اللائحة الصريحة لا في كل الأحداث، ووجّهوا حسب Chari-Event-Type، وأزيلوا التكرار على Chari-Event-Id. وتحقّقوا من التوقيع قبل أي معالجة، ولا تبنوا شيئًا على حقول الجسم غير metadata الخاص بكم.

كيف أعرف أن تاجرًا فرعيًا قد قُبل؟

عبر الحدث wallet.activated، الذي يُخطر بتفعيل محفظته بعد التدقيق؛ أما wallet.rejected فيُخطر بالرفض. ولا ننشر مدة للتدقيق. اربطوا هذين الحدثين بحالة التاجر الفرعي في قاعدتكم، ولا تفتحوا مبيعاته في الإنتاج إلا بعد التفعيل. وإلى ذلك الحين، أبقوا التاجر الفرعي في حالة «قيد التدقيق» داخل منصتكم.

هل يذكر رد إنشاء الجلسة التاجرَ الفرعي المستفيد؟

لا. يتضمن الرد خمسة حقول ولا شيء غيرها: sessionId وsessionToken وverifyKey وexpiresAt وcheckoutUrl. ثم تُقرأ الحالة والمبلغ عبر GET /v1/payment-sessions/{sessionId}. احتفظوا أنتم بالربط بين sessionId والتاجر الفرعي، وأضيفوا مرجعه إلى metadata الذي يعود كما هو في حدث الأداء: فتجدون التاجر الفرعي في كل حدث أداء دون بحث يدوي.

كيف أسترجع أداءات تاجر فرعي؟

عبر قاعدة بياناتكم، التي تغذّيها الأحداث: ضعوا مرجع التاجر الفرعي في metadata الذي يعود كما هو في حدث الأداء، ويحمل حدث الجلسة أيضًا orderId الخاص بكم. وبعد أي انقطاع، طابقوا عبر GET /v1/transactions. ويُعَدّ فضلًا عن ذلك لكل تاجر فرعي كشف شهري مقصور على نشاطه هو.

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

اكتبوا جلساتكم ومستقبِل الويب هوك على السندبوكس: ابدؤوا في وضع الاختبار، ثم ادفعوا ببطاقة الاختبار. وبالتوازي، حضّروا تركيب التوزيع مع فريق الإدماج، انطلاقًا من صفحة التوزيع والتجار الفرعيون ومن وحدة «جلسات الدفع» في توثيق API.

بقلم فريق ChariPay.

اقرؤوا بعده

مستعدون للتجربة بأنفسكم؟

أنشئوا حسابكم واستخلصوا في وضع الاختبار اليوم: مجانًا، ودون موافقة تنتظرونها. سؤال؟ يجيب فريقنا التجّار والمطوّرين على حد سواء.

  • مجاني ودون التزام
  • وضع الاختبار منذ التسجيل
  • لا موافقة تنتظرونها