نظرة عامة
يقوم الإرسال من جهة العميل على ثلاثة ضمانات:- ربط هوية مقاوم للانتحال. يُحدَّد الـtoken عند الإصدار بقيمة
vendor_dataواحدة (معرّف المستخدم لديك). تُفرض هوية الموضوع لكل معاملة تُرسل بهذا الـtoken من الخادم استناداً إلى هذا الربط، فلا يستطيع عميل تم العبث به تقديم معاملات نيابةً عن مستخدم آخر، ولا تحتاج الـpayload إلى حمل أي بيانات اعتماد. - ذكاء جهاز تلقائي. تجمع الحزمة بصمة الجهاز وترسلها مع الإرسال، ويستخلص Didit عنوان IP ووكيل المستخدم من الخادم. يُرفق سياق الجهاز المجمّع بموضوع المعاملة دون أي مكتبة إضافية، أو رمز جهاز منفصل، أو أي ربط برمجي من جانبك. ويدعم هذا السياق القواعد المبنية على الجهاز، والربط بين الجلسات والمعاملات حسب الجهاز.
- إثبات السيطرة على المحفظة الذي يُفتح تلقائياً. عندما تتطلب المعاملة التي أُنشئت أن يُثبت المستخدم النهائي سيطرته على محفظة، تتضمن الاستجابة كتلة
action_requiredمن النوعwallet_ownership، فتفتح الحزمة هذا المسار فوراً، ثم تحدّث المعاملة عند انتهائه، دون الحاجة إلى حلقات استعلام متكررة أو إلى بناء آلية لإرسال الروابط بنفسك. يختلف إجراءverification_sessionالمطلوب عن ذلك: فهو لا يُشغَّل تلقائياً أبداً، ويعود في الكتلة نفسها ليشغّله تطبيقك باستخدام تكامل التحقق الخاص به.
يبقى
POST /v3/transactions/ على الخادم الخيار الصحيح عندما لا يكون مستخدم نهائي حاضراً، مثل التحويلات الإدارية، واستيراد الدفعات، أو الأحداث التي تبلغ بها أنظمتك الداخلية.
يُنشئ المساران سجلات المعاملات نفسها، ويطبّقان القواعد نفسها، ويصدران أحداث webhook نفسها.التدفق
1
إصدار رمز المعاملة
يستدعي خادمك الخلفي
POST /v3/transactions/sdk-token/ باستخدام مفتاح API الخاص بك وقيمة vendor_data للمستخدم، ويمرر sdk_token المُعاد إلى تطبيقك.2
الإرسال من الجهاز
يستدعي التطبيق
submitTransaction في SDK، فترسل الحزمة الـpayload إلى POST /v1/transactions/ مع الرأس X-Transaction-Token مع الإثراء التلقائي لبيانات الجهاز.3
معالجة أي إجراء مطلوب
إذا كانت قيمة
action_required.type هي wallet_ownership، فإن الحزمة تفتح الواجهة تلقائياً، ثم تحدّث المعاملة عبر GET /v1/transactions/{transaction_id}/.
وإذا كانت verification_session، فلن تشغّل الحزمة أي شيء؛ اقرأ action_required وابدأ مسار التحقق الخاص بك باستخدام session_token (أو url على الويب).إصدار رمز المعاملة
أصدِر tokens من خادمك الخلفي باستخدامPOST /v3/transactions/sdk-token/، وذلك عبر المصادقة نفسها المعتمدة على مفتاح API في بقية الـendpoint الخاصة بالمعاملات في /v3/.
تُقبل أيضاً الأسماء بصيغة camelCase (
vendorData، ttlSeconds، maxUses) كما في بقية واجهات /v3/.
الإرسال من الجهاز
ترسل حزم SDK الطلبات إلىPOST /v1/transactions/ مع الـ token في الترويسة X-Transaction-Token.
الـ payload بصيغة camelCase، والطرق submitTransaction تبنيه عنك، ولا تحتاج إلى التعامل معه مباشرة إلا عند استدعاء الـ endpoint من عميل HTTP خاص بك:
POST /v3/transactions/:
تأتي الاستجابة بتفاصيل المعاملة نفسها بصيغة snake_case التي تستخدمها نقاط
/v3/، مضافًا إليها الكتلة action_required:
الإعلان عن محفظة ذاتية الاستضافة
يُعلن المثال أعلاه أن الوجهة هي المحفظة ذاتية الاستضافة للمستخدم: قيمةpaymentMethod.type لدى الطرف المقابل هي unhosted_wallet.
يغيّر هذا الإعلان طريقة تنفيذ تبادل Travel Rule:
- تتخطى Didit توجيه الطرف المقابل عبر VASP كليًّا، إذ لا يوجد VASP خلف المحفظة ذاتية الاستضافة، وتُنشئ التحويل مباشرةً بالحالة
UNCONFIRMED_OWNERSHIP. - عند تفعيل
auto_wallet_verificationفي إعدادات Travel Rule (وهو الإعداد الافتراضي)، تُنشأ تلقائيًّا جلسة أداة إثبات ملكية المحفظة وتُعاد فيaction_required، تمامًا كما في الاستجابة أعلاه. - يُشغّل SDK الأداة تلقائيًّا؛ وبعد أن يُثبت المستخدم سيطرته على المحفظة، تُجرى مطابقة الاسم للتحويل ويتقدم إلى
COMPLETED، أو إلىCOUNTERPARTY_MISMATCHED_DATAإذا لم يطابق الاسم صاحب المحفظة في دفتر عناوينها.
فرض الجهة المعنية
تُحسم ثلاثة أمور دائمًا بواسطة الـ token، لا بواسطة الـ payload:- التطبيق الذي تُنشأ المعاملة ضمنه.
- هوية الجهة المعنية: القيمة
subject.externalUserIdفي الـ payload تُتجاهل وتُستبدل بالقيمةvendor_dataالخاصة بالـ token. - سياق الجهاز الخاص بالجهة المعنية (انظر أدناه)؛ القيم المشتقة من الخادم تغلب عند التعارض مع أي قيمة واردة في
subject.device.
401 من هذا الـ endpoint أن الـ token رُفض: إما لأنه مفقود أو غير معروف أو ملغى أو تجاوز حد max_uses الخاص به أو منتهي الصلاحية.
تحتوي ردود الـ token المنتهي دائمًا على الكلمة expired في تفاصيل الخطأ، وبذلك تميّز SDK بين أخطاء expired_token وinvalid_token.
قراءة المعاملة مرة أخرى
يعيدGET /v1/transactions/{transaction_id}/ التفاصيل نفسها، بالمصادقة نفسها عبر X-Transaction-Token.
تستدعي SDK هذا الـ endpoint تلقائيًّا بعد اكتمال مسار الإجراء الذي أُطلق أو انتهاء نافذته، فلا تتوقف الحالة النهائية على حدث واحد أو إشعار واحد.
استخدمه بنفسك لإعادة التحقق من status وaction_required في أي وقت ما دام الـ token صالحًا؛ ومن الخادم الخاص بك، يعيد GET /v3/transactions/{transaction_id}/ السجل نفسه.
ذكاء الجهاز التلقائي
تتضمن كل عملية إرسال بيانات ذكاء الجهاز دون الحاجة إلى أي عمل تكامل من جانبك:- يجمع SDK بصمة جهاز تحافظ على الخصوصية (مخطط
didit-fp-v2: إشارات المنصة والمتصفح، ومعرّف جهاز دائم، ودرجة الروبوتية، وتجزئة مركّبة)، ويرسلها مع الطلب. - تستنتج Didit عنوان IP ووكيل المستخدم (user agent) ورأس accept-language من جهة الخادم، انطلاقًا من الاتصال نفسه، لذلك لا يمكن تزويرها عبر الـ payload.
/v3/ أن تواصل تمرير كائنات device_context صريحة لكل مشارك؛ أما عبر مسار SDK فتحصل على نسخة أقوى من البيانات نفسها دون أي جهد إضافي.
الإجراءات المطلوبة من المستخدم
قد تتطلب بعض المعاملات أن يقوم المستخدم النهائي بإجراء قبل أن تُسوّى: يمكن لقاعدة أن تشترط جلسة تحقق، ويمكن لتحويل وفق Travel Rule أن يشترط إثباتاً بأن المحفظة الوجهة تعود فعلاً للمستخدم. تظهر الحالتان في المكان نفسه: كتلةaction_required في استجابة الإرسال (وفي كل قراءة لاحقة للمعاملة):
إذا انطبق الاحتمالان على المعاملة نفسها، يكون لـ
wallet_ownership الأولوية، لأن غياب إثبات الملكية يمنع تبادل Travel Rule نفسه.
يصبح action_required بقيمة null بعد حلّ الإجراء المعلّق.
يُبقى الحقل remediation الموجود للتوافق مع الإصدارات السابقة؛ وaction_required هو الكتلة المعتمدة.
عقد التشغيل التلقائي
autoLaunchAction (القيمة الافتراضية true) يشغّل تلقائياً الإجراء wallet_ownership فقط:
- الويب - يُفتح في النافذة المنبثقة الخاصة بـ SDK، ويُكتشف الإكمال عبر المراسلة المتحقَّق من مصدرها إضافةً إلى استطلاع محدود للمعاملة (ولا يعتمد أبداً على حدث واحد).
- iOS / Android (وغلافا React Native وFlutter، اللذان يفوّضان إلى SDK الأصلية) - يُعرض كـتجربة إثبات ملكية المحفظة الأصلية لدى SDK (SwiftUI / Jetpack Compose) عندما تكون
useNativeWalletOwnershipقيمتهاtrue(وهي الافتراضية)، مع الرجوع إلىSFSafariViewController/ Chrome Custom Tab، وأخيراً إلى المتصفح الافتراضي. بعد إغلاق التدفق أو عودة التطبيق إلى المقدمة، يعيد SDK قراءة المعاملة تلقائياً.
verification_session تلقائياً أبداً: يعود دائماً في action_required، ليبدأه تطبيقك بتكامل التحقق الخاص بـ Didit.
بعد أن يكتمل تدفق إثبات ملكية المحفظة الذي يشغّله SDK تلقائياً أو يُغلق، يعيد SDK قراءة المعاملة ويمرّر النتيجة المحدَّثة إلى دالة الاستدعاء onActionCompleted لديك.
لا يُعدّ إجراء verification_session جزءاً من هذه الدورة، لأن تطبيقك هو من يشغّله - راجع القسم التالي.
تشغيل جلسة التحقق
عندما يكونaction_required.type هو verification_session، اقرأ الكتلة من result.actionRequired وسلّمها إلى تكامل التحقق القائم لديك من Didit:
GET /v1/transactions/{transaction_id}/ (أو انتظر webhook المعاملة) للحصول على الحالة المحدَّثة للمعاملة.
مثال: التحقق البيومتري الإضافي لسحب عالي القيمة
هذا هو التدفق في أفضل صوره: أخضِع المعاملات عالية المخاطر - المبالغ الكبيرة، والمستفيدون الجدد، والأجهزة الجديدة - لإعادة تحقق بيومتري من الشخص نفسه الذي تحققت منه عند التسجيل. الاستيلاء على الحساب يبدو تماماً هكذا: كلمة مرور صحيحة، ورمز 2FA صحيح (تم الحصول عليه بالتصيّد أو عبر تبديل بطاقة SIM)، وسحب إلى محفظة لم تراها من قبل. كل دفاع يعتمد على بيانات الاعتماد يكون قد فشل بحلول تلك اللحظة. أما الوجه فلا يزال صامداً: لا يستطيع المهاجم اجتياز صورة سيلفي خضعت لكشف الحيوية يجب أن تطابق الوجه الملتقط أثناء تسجيل KYC للمالك الشرعي.1
أنشئ القاعدة مرة واحدة، في الكونسول
Transactions → Rules → Create Rule: الشرط
amount gte 5000، والإجراء Change status → AWAITING_USER، واختر سير العمل المصادقة البيومترية على الإجراء.
أضف أي شروط مخاطر أخرى تريدها - طرفاً مقابلاً لأول مرة، وبصمة جهاز جديدة، وتغيّر دولة عنوان IP - كشروط إضافية أو كقواعد إضافية.2
أرسل السحب كالمعتاد
لا يتغير شيء في كود الإرسال لديك. عندما تُطبَّق القاعدة، تعود الاستجابة بـ
status: "AWAITING_USER" وكتلة action_required من النوع verification_session:3
شغّل إعادة التحقق
سلّم
session_token (أو url) إلى تكامل التحقق القائم لديك من Didit، تماماً كما في تشغيل جلسة التحقق.
بما أن الجلسة تُنشأ بالـ vendor_data نفسه الخاص بموضوع المعاملة، فإن Didit يحتفظ بالفعل بالوجه المرجعي المتحقَّق منه للمستخدم من التسجيل - صورة السيلفي المعتمدة مع كشف الحيوية، أو الصورة الموجودة على شريحة جواز السفر، أو الصورة الشخصية الموجودة على المستند.
يلتقط المستخدم صورة سيلفي حية واحدة؛ يُجري Didit كشف الحيوية ومطابقة الوجه بنسبة 1:1 مع ذلك المرجع المخزّن. لا إعادة لمسح المستند ولا إعادة تسجيل - عبء لا يتجاوز بضع ثوانٍ، فقط على المعاملات التي تستحقه.4
احسم المعاملة بحسب النتيجة
عند اكتمال الجلسة، يُعاد تقييم المعاملة تلقائياً:
APPROVED إذا نجحت إعادة التحقق، وDECLINED إذا فشلت، مع إرسال webhook بالحدث transaction.status.updated في الحالتين (reason: remediation_completed في الجدول الزمني للمعاملة).
يستلم تطبيقك النتيجة عبر دوال الاستدعاء على نمط onActionCompleted، أو عبر GET /v1/transactions/{transaction_id}/، أو عبر الـ webhook.Web SDK
submitTransaction متاحة اليوم في كائن Web SDK الفريد (المفرد):
{ transactionId, status, travelRuleStatus?, actionRequired? }.
في حمولة مستوى SDK تكتب category وdetails وcustomProperties، ويتولى SDK تحويلها إلى الأسماء المستخدمة في الشبكة (type وinfo وprops) نيابةً عنك.
تُصنَّف الأخطاء عبر DiditTransactionError:
SDKs الأصلية
تأتي واجهة
submitTransaction الأصلية مع iOS وAndroid SDK 4.1.0 (وكذلك إصدارات React Native وFlutter التي تثبّتها).
على عكس Web SDK، تستخدم الحمولات الأصلية أسماء الحقول الشبكية مباشرة: type وinfo وprops.getTransaction(transactionToken, transactionId) لإعادة قراءة المعاملة بالـ token نفسه. تستدعيها الـSDKs نيابةً عنك بعد تشغيل مسار إجراء، ويمكنك استدعاءها بنفسك في أي وقت.
على الأجهزة المحمولة، يُطبَّق نقل بصمة الجهاز بالفعل على جميع استدعاءات الشبكة في SDK، لذلك تحمل طلبات إرسال المعاملات بيانات ذكاء الجهاز نفسها تلقائيًا.
بدون SDKs
كل ما تفعله SDKs هو HTTPS عادي، ولا يتطلب أيٌّ من ذلك استخدامها. هناك شكلان متكافئان للتكامل: من جانب الخادم فقط (دون أي token). أرسل المعاملة عبرPOST /v3/transactions/ من الخادم الخلفي باستخدام مفتاح API الخاص بك. تحمل الاستجابة، وكذلك كل قراءة لاحقة عبر GET /v3/transactions/{transaction_id}/، كتلة action_required نفسها. يمكنك أيضًا تمرير سياق device صريح لكل مشارك، لأنه لا يوجد SDK يجمعه نيابةً عنك.
من جانب العميل باستخدام مكدس HTTP الخاص بك. أنشئ token عبر POST /v3/transactions/sdk-token/، ثم استدعِ POST /v1/transactions/ وGET /v1/transactions/{transaction_id}/ من الجهاز مع الرأس X-Transaction-Token. تنطبق الآلية نفسها لربط الموضوع نفسه واشتقاق عنوان IP ووكيل المستخدم من جهة الخادم.
ثم تعامل مع action_required بنفسك:
ثم اكتشف الإتمام دون أي استدعاء من العميل:
- webhook (موصى به). يُطلَق
transaction.status.updatedعندما يحسم الإجراء المعلّق المعاملة، ويُطلَقtravel_rule.status.updatedعند كل انتقال في التبادل. يتضمن كلاهما ما يكفي لتحديث سجلاتك دون أي استطلاع. - الاستطلاع. أعد قراءة المعاملة (
/v3/من خادمك الخلفي، أو/v1/مع token ما دام صالحًا). تعودaction_requiredإلىnullبعد الحسم، وتحملstatusوtravel_rule.statusالنتيجة.
الإشعارات عبر webhook
لا يحتاج خادمك الخلفي إلى أن يعيد التطبيق الإبلاغ. عندما تتطلب معاملة مُرسلة إجراءً من المستخدم، يتضمن webhook المعاملة، أيtransaction.created أو transaction.status.updated، كتلة action_required نفسها، فتُطلِع أنظمتك على الإجراء المعلّق حتى لو أُغلق التطبيق قبل انتهاء المسار. يأتي إجراء verification_session مع الحالة AWAITING_USER، لكن إجراء wallet_ownership لا يغيّر حالة المعاملة وحده، لذلك اقرأ action_required بدلاً من الحالة.
تصدر انتقالات Travel Rule أيضًا travel_rule.status.updated.
الخطوات التالية
إرسال المعاملات
واجهة إنشاء المعاملة من جهة الخادم ومرجع الحمولة الكامل.
Travel Rule
التبادل المُدار بين المُرسِل والمستفيد، والإعدادات، ودورة الحياة.
أداة ملكية المحفظة
مسار إثبات التحكم المستضاف الذي يقف وراء إجراءات wallet_ownership.
webhook
transaction.created وtransaction.status.updated، والتحقق من التوقيع.
القواعد
إجراءات القواعد، بما فيها الإجراءات التي تنشئ جلسات التحقق.
الحالات
ما معنى AWAITING_USER في دورة حياة المعاملة.