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

ربط الأداء بالطلبية: orderId وexternalId وmetadata وباقي المعرّفات

ربط الأداء بالطلبية دون خطأ: من ينشئ orderId وexternalId وmetadata وoperationId، وأين يعود كل منها، حتى الاسترداد وسطر تصدير CSV.

لربط الأداء بطلبيته، ضعوا مرجعكم الخاص في الحقل metadata للأداء: فهو يعود إليكم كما هو في حدث الأداء، وبالنسبة إلى جلسة الدفع يحمل الحدث أيضًا orderId الخاص بكم. أما externalId فوظيفته ألا تُنشئوا المورد نفسه مرتين؛ ولا يعود في حدث الجلسة ولا في حدث الرابط. يرتّب هذا الدليل المعرّفات السبعة للأداء — من ينشئها، وما فائدتها، وأين تعود —، ثم يطبّقها على الجلسة، والرابط، والاشتراك، والمتجر المستضاف، وصولًا إلى الاسترداد وسطر تصدير CSV.

ربط الأداء بالطلبية: جدول المعرّفات

ChariPay منصة دفع مغربية، مزوّد خدمات دفع (PSP) تشغّله Chari Money، مؤسسة أداء مرخَّصة من بنك المغرب. والأداء فيها يعبر عدة أنظمة — موقعكم، وصفحة الدفع، والويب هوك، والتصدير المحاسبي —، ولكل منها معرّفه. والخلط بينها هو ما يصنع الأداءات «اليتيمة»: المال موجود، والطلبية ما زالت تنتظر. هذه هي المعرّفات السبعة التي ينبغي معرفتها.

المعرّفمن ينشئهفائدتهأين يعود
orderIdأنتم، عند إنشاء جلسةمرجعكم التجاري، دون قيد تفرّدفي حدث أداء الجلسة
externalIdأنتم، على رابط أو جلسة أو اشتراك أو منتجألا يُنشأ المورد نفسه مرتين؛ فريد لكل حساب ولكل بيئةفي قراءات الرابط؛ وفي أحداث الاشتراك؛ لا في حدث الجلسة ولا في حدث الرابط
metadataأنتمحمل مراجعكم الحرة: السلة، والطلبية، وقناة البيعكما هو، في القراءات وفي حدث الأداء
reference (pl_…)ChariPay، عند إنشاء رابطتحديد الرابط وقراءة حالتهرد الإنشاء؛ GET /v1/payment-links/{reference}
sessionId (ps_…)ChariPay، عند إنشاء جلسةتحديد الجلسة وقراءة حالتهارد الإنشاء؛ GET /v1/payment-sessions/{sessionId}
operationIdChariPay، عند الأداءتحديد الأداء، ولا سيما لاستردادهGET /v1/transactions؛ GET /v1/transactions/{operationId}
refundReferenceأنتم، عند طلب الاستردادجعل الاسترداد قابلًا للإعادة دون خصم مزدوجGET /v1/refunds/{reference}، الذي يقبل refundReference الخاص بكم

قاعدتان تلخّصان الجدول. الأولى: اعثروا على طلبيتكم عبر metadata الخاص بكم، الذي يُعاد كما هو في الحدث؛ وبالنسبة إلى جلسة، يحمل الحدث أيضًا orderId الخاص بكم. والثانية: externalId الخاص بجلسة أو برابط لا يعود في الحدث — فهو لمنع التكرار، لا للربط؛ والاشتراك وحده استثناء. وما يحميكم منه externalId، وكيف، مشروح في Idempotency في المدفوعات.

metadata الأداء: كائن مسطّح وخفيف يُعاد كما هو

الحقل metadata كائن ترفقونه عند إنشاء رابط أو جلسة أو اشتراك أو منتج. وقواعده صارمة:

  • مسطّح: نصوص وأرقام وقيم منطقية؛ لا كائن متداخل ولا مصفوفة، وإلا فشل الإنشاء بالرد 400؛
  • خفيف: 4 كيلوبايت على الأكثر؛
  • في الجذر: يُرسل مرة واحدة، في المستوى الجذري للطلب، لا داخل config؛
  • يُعاد كما هو: في القراءات وفي حدث الأداء، دون أي تحويل.
ما يوضع فيهما يُتجنَّب
مراجعكم الداخلية: cartId وorderNumberاسم الزبون أو هاتفه أو بريده، فلها حقولها الخاصة
قناة البيع: "source": "web" أو "source": "whatsapp"رقم بطاقة، أو CVV، أو أي معطى من معطيات البطاقة
معرّف متجر أو صندوقسرّ، أو رمز، أو مفتاح
مؤشّر تجاري منطقي: "gift": trueكائن متداخل أو قائمة

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

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

جلسة الدفع: orderId وmetadata في الحدث

تحمل الجلسة معرّفاتكم الثلاثة منذ إنشائها:

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"
      }
    },
    "metadata": { "cartId": "c_987", "source": "web" }
  }'

ORD-2026-0421 هو مرجع طلبيتكم، وorder-2026-0421 مفتاحكم لمنع التكرار، وcartId وsource مراجعكم الحرة. ولا يحتوي الرد إلا على خمسة حقول — sessionId وsessionToken وverifyKey وexpiresAt وcheckoutUrl —: سجّلوا sessionId مقابل الطلبية، فستحتاجونه لإعادة قراءة الجلسة. وحقول الإنشاء موصوفة في وحدة «جلسات الدفع» في توثيق API.

وحين ينجح الأداء، يحمل إليكم الحدث payment.succeeded الحقل metadata الخاص بكم كما هو، وorderId الخاص بكم — أو، إن حدّدتموها، قيمة config.urls.externalReference التي تحلّ محله. ولا يُقرأ معرّف الحدث ونوعه من الجسم، بل من الترويستين Chari-Event-Id وChari-Event-Type. وفي مستقبِلكم، يبدو السطر الذي يمسّ طلبيتكم هكذا:

javascript
await orders.markPaid(event.metadata.cartId);   // your metadata, echoed verbatim

ويندرج هذا السطر في مستقبِل كامل — التحقق من التوقيع على الجسم الخام، وإزالة التكرار، وتأكيد الاستلام — موصوف في إنجاح دمج الويب هوك. وجسم الحدث JSON مسطّح لا تُنشر قائمة حقوله: فاعتمدوا في الربط على metadata الخاص بكم، وابحثوا عن معرّف الحدث في Chari-Event-Id، لا في أي مكان آخر.

رابط الدفع: reference الرابط وmetadata الخاص به

يتبع الرابط المنطق نفسه، بمعرّفاته الخاصة:

bash
curl -X POST 'https://api-psp.charipay.ma/v1/payment-links' \
  -H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-1042-link' \
  -d '{
    "amount": 149.90,
    "description": "Commande #1042",
    "singleUse": true,
    "externalId": "order-1042",
    "customerEmail": "amine.bennani@example.com",
    "customerPhone": "+212600000000",
    "metadata": { "cartId": "c_987" }
  }'

يعطيكم الرد — 201، أو 200 إن أعدتم externalId نفسه — reference الرابط (pl_…) وpayUrl الخاص به، أي الرابط الذي يُشارَك. سجّلوا reference مقابل الطلبية: فهو الذي يسمح بقراءة حالة الرابط عبر GET /v1/payment-links/{reference}.

وحين يؤدّي الزبون بالبطاقة، يحمل حدث الأداء metadata الرابط — هنا cartId —، لا externalId الخاص به. فربطكم يعتمد إذن على metadata، أو على reference الذي احتفظتم به. وبالنسبة إلى رابط لاستعمال واحد يُسدَّد نقدًا في الوكالة، تابعوا حالة الرابط: بمجرد أن تؤكد الشبكة الإيداع، يصبح الرابط «مدفوعًا» ويصلكم الإشعار في بوابة التاجر؛ ومن جهة الخادم تُقرأ حالة الرابط عبر GET /v1/payment-links/{reference}. ومسار الزبون في هذه الحالة مفصّل في الاستخلاص نقدًا في الوكالة.

الاشتراكات والمتجر المستضاف: استثناءان ينبغي معرفتهما

الاشتراكات. في الاشتراك، يعود externalId الخاص بكم وmetadata الخاص بكم في الحدثين subscription.payment_succeeded وsubscription.payment_failed: وهذا موثَّق، وهو الاستثناء الوحيد من قاعدة externalId. فيرتبط كل استحقاق مباشرة بالاشتراك في نظامكم، دون جدول مطابقة. والنداءات والأحداث مفصّلة في دمج الاشتراكات عبر API.

المتجر المستضاف. ترافق الطلبيةَ المؤدّاة بالبطاقة في المتجر المستضاف حدثان: order.paid، الذي يعلن أن الطلبية مدفوعة، وpayment.succeeded المرافق، الذي يحمل metadata المنتج. فإن كان ربطكم يعتمد على metadata — معرّف مستودع، أو مرجع في الكتالوج —، فاقرؤوه في payment.succeeded، لا في order.paid. والأداة نفسها معروضة في المتجر المستضاف.

من الأداء إلى الاسترداد: إيجاد operationId

للاسترداد، يجب تحديد الأداء الأصلي. فضّلوا operationId الخاص به، وهو معرّف الأداء من جهة ChariPay. ويُقرأ في GET /v1/transactions — قائمة قابلة للترشيح بـstatus وtype وmethod وchannel وfrom وto وsearch، تُتصفَّح بالمؤشّر limit / cursor / hasMore — أو في GET /v1/transactions/{operationId} الذي يفصّل معاملة واحدة؛ ويرسم GET /v1/transactions/{operationId}/timeline تسلسلها الزمني. وكل حركة تحمل مرجع البيع الذي يفسّرها: حدّدوا أداء طلبيتكم مرة واحدة، ثم احفظوا operationId الخاص به مقابلها كي لا تبحثوا عنه مجددًا.

وفي غياب operationId، يقبل طلب الاسترداد حقل externalId: وهو يعني حينها المرجع التجاري للأداء — بالنسبة إلى جلسة، قيمة orderId الخاص بكم، أو قيمة config.urls.externalReference إن حدّدتموها —، لا externalId منع التكرار الذي أعطيتموه للجلسة أبدًا. وهنا يكمن الفخ: اسم الحقل نفسه يعني شيئين مختلفين حسب النداء.

وأخيرًا، يُشتق refundReference الخاص بكم من الطلبية — refund-ORD-2026-0421-1 —، لا من سحب عشوائي أبدًا: فهو الذي يجعل الاسترداد قابلًا للإعادة، بالرد 202 عند الإنشاء و200 عند الإعادة، ثم refund.succeeded أو refund.failed في النهاية. والمسطرة كاملة، من بوابة التاجر أو عبر API، في استرداد مبلغ لزبون.

وصولًا إلى سطر تصدير CSV

تحمل كل حركة في الحساب مرجع البيع أو الاسترداد أو الدفعة التي تفسّرها؛ وتميّز بوابة التاجر بين المبلغ المدفوع والرسوم والصافي، ويحتفظ تصدير CSV بهذه المراجع — من بوابة التاجر أو عبر GET /v1/transactions/export.csv (حتى 10 000 سطر؛ وفوق ذلك قسّموا حسب الفترة بـfrom/to). والطوابع الزمنية بتوقيت UTC؛ والمغرب على UTC+1: فعلى أداة المطابقة لديكم أن تحوّل.

ثلاث نتائج لربطكم:

  • الاسترداد حركة مستقلة، مرتبطة بالمعاملة الأصلية، وقد يخفّض الدفعة التالية دون سطر منفصل في كشف الحساب البنكي؛
  • يشترط التصدير عبر API مفتاحًا يملك الصلاحية `transaction:export`: ودونها يجيب النداء بـ403 FORBIDDEN؛
  • الأداء الذي يتمّ بعد منتصف الليل بقليل، بتوقيت المغرب، يظهر في اليوم السابق في التصدير الخام، ما لم تحوّلوا التوقيت.

أما الطريقة اليومية — الرسوم، وتوقيت الدفعات، والاستردادات — فمكانها مطابقة المستخلصات: ويتوقف هذا الدليل عند المعطيات التي تجعلها ممكنة. ومعاملات القائمة والتصدير في وحدة «المعاملات» في توثيق API.

خمسة أخطاء في الربط ينبغي تجنّبها

  1. 1انتظار `externalId` في الحدث. بالنسبة إلى جلسة أو رابط، لا يعود فيه: اربطوا عبر metadata، وعبر orderId بالنسبة إلى جلسة.
  2. 2وضع كائن متداخل في `metadata`. يفشل الإنشاء بالرد 400: احتفظوا بكائن مسطّح من النصوص والأرقام والقيم المنطقية.
  3. 3إعادة استعمال `orderId` نفسه لطلبيتين. تقبله API، لأن orderId لا يخضع لقيد التفرّد؛ لكن ربطكم يصبح ملتبسًا.
  4. 4الاسترداد بـ`externalId` منع التكرار الخاص بجلسة. استعملوا operationId، وإلا فالمرجع التجاري للأداء: orderId الخاص بكم، أو config.urls.externalReference إن حدّدتموها.
  5. 5إزالة التكرار على أساس `Chari-Webhook-Id`. أزيلوا التكرار على أساس Chari-Event-Id، لا على أساس Chari-Webhook-Id — فهذا الأخير يحدّد التسليم، لا الحدث.

وللخطأين 1 و5 جذر واحد: استعمال معرّف بدل آخر — مفتاح منع التكرار بدل مرجع الطلبية، ومعرّف التسليم بدل معرّف الحدث. والفرق بين منع التكرار وإيجاد الطلبية مفصّل في Idempotency في المدفوعات.

أسئلة شائعة

ما الفرق بين orderId وexternalId؟

orderId هو مرجعكم التجاري للجلسة: لا يخضع لقيد التفرّد، ويحمله حدث الأداء. أما externalId فمفتاح لمنع التكرار، فريد لكل حساب ولكل بيئة: الإنشاء بـexternalId موجود يُرجع المورد الموجود بالرد 200. وبالنسبة إلى جلسة أو رابط، لا يعود externalId في الحدث، فلا تعتمدوا عليه للربط.

هل يعود externalId في ويب هوك الأداء؟

لا بالنسبة إلى جلسة أو رابط: اعثروا على طلبيتكم عبر metadata الخاص بكم، الذي يُعاد كما هو في الحدث؛ وبالنسبة إلى جلسة، يحمل الحدث أيضًا orderId الخاص بكم. والاستثناء الوحيد هو الاشتراك: يعود externalId وmetadata في subscription.payment_succeeded وsubscription.payment_failed، كما يوثّق ذلك عقد API.

ماذا يمكن وضعه في metadata الأداء؟

كائن مسطّح — نصوص وأرقام وقيم منطقية —، بـ4 كيلوبايت على الأكثر، دون كائن متداخل ولا مصفوفة، وإلا كان الرد 400. ضعوا فيه مراجع مبهمة: معرّف السلة، ورقم الطلبية، وقناة البيع، والمتجر. ولا تضعوا فيه أبدًا معطيات البطاقة ولا الأسرار، وتجنّبوا المعطيات الشخصية، لأن محتواه يعود كما هو في الأحداث.

كيف أجد operationId الخاص بأداء؟

عبر GET /v1/transactions، بالترشيح حسب الفترة أو الحالة أو النوع أو وسيلة الأداء، وتصفّح القائمة بالمؤشّر؛ أو عبر GET /v1/transactions/{operationId} للتفاصيل. وكل حركة تحمل مرجع البيع الذي يفسّرها: حدّدوا الأداء مرة واحدة، ثم احفظوا operationId الخاص به مقابل الطلبية، فهو ما يحتاجه الاسترداد.

كيف أعرف أي طلبية أُدّيت عبر رابط دفع؟

احتفظوا بـreference الرابط (pl_…) مقابل الطلبية منذ إنشائه. ويحمل حدث الأداء metadata الرابط، لا externalId الخاص به، وتُقرأ حالة الرابط في أي وقت عبر GET /v1/payment-links/{reference}. وفي الأداء نقدًا في الوكالة، يصبح الرابط «مدفوعًا» ويصلكم الإشعار في بوابة التاجر. وفي الحالتين، تربطون الأداء بطلبيته عبر reference المحفوظ أو عبر metadata.

أين أقرأ معرّف حدث الويب هوك؟

في الترويسة Chari-Event-Id، الثابتة عبر كل المحاولات وكل نقاط الاستقبال لديكم؛ ونوع الحدث في Chari-Event-Type. لا تبحثوا عنه في الجسم. أما Chari-Webhook-Id فيحدّد التسليم: ولا يصلح لإزالة التكرار. وعلى جدول إزالة التكرار لديكم أن يضع قيد التفرّد على Chari-Event-Id، كي لا يُعالَج الحدث نفسه مرتين.

هل أحتاج إلى orderId مختلف لكل جلسة؟

ليس بالضرورة. فـorderId لا يخضع لقيد التفرّد: يمكن لجلسة جديدة تُنشأ للطلبية نفسها، بعد انتهاء صلاحية أو إلغاء، أن تحتفظ بـorderId نفسه. أما externalId الخاص بها فيجب أن يتغير، وإلا أرجعت API الجلسة القديمة؛ وغيّروا أيضًا Idempotency-Key، وإلا ردّت API بـ409 IDEMPOTENCY_CONFLICT. ولا ينبغي أبدًا لطلبيتين مختلفتين أن تتقاسما orderId واحدًا.

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

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

بقلم فريق ChariPay.

اقرؤوا بعده

تقني4 دقائق قراءة

لا تحصّلوا مرتين أبدًا — شرح Idempotency

شرح Idempotency في المدفوعات: كيف تمنع ترويسة Idempotency-Key وحقل externalId خصم المبلغ من الزبون مرتين حين تنقطع الشبكة في منتصف النداء.

دليل4 دقائق قراءة

مطابقة تحصيلاتكم مع محاسبتكم

مطابقة تحصيلاتكم مع محاسبتكم: الفوارق الثلاثة بين المبلغ المحصَّل والتحويل الواصل إلى البنك، وطريقة يومية تجعل الأرقام تتوازن دون عناء.

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

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

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