دليل ربط منصات SaaS العامة مع BeanzPay
هذا الدليل مخصص لأي منصة SaaS تريد إضافة BeanzPay كطبقة دفع وإدارة تاجر. صُمم ليُستخدم في بناء Connector مستقل من الخادم إلى الخادم (S2S)، مع رحلة موافقة مالك التاجر المطلوبة عند إصدار اعتماد مالي أو توسيع نطاقاته. لا يطلب الدليل من المنصة جمع بيانات البطاقة، ولا يضع اسم مزود الدفع في تجربة التاجر أو العميل النهائي.
تحديث الدليل: 21 أكتوبر 2026. مرجع خط أساس التشغيل المراجع: 28959793176ed4894b39ce1210b88afef60188d3.
متطلبات هوية المالك في هذه المراجعة هي عقد Partner API 1.9.0؛ تحقق من نسخة
الاكتشاف الفعلية قبل تطبيقها. سجل النشر النهائي يحدد الإصدار الذي نُفذ عليه التحقق.
هذا تحديث توثيقي للعقد، ولا يمثل اختبار قبول مالي مشتركًا أو تفعيل كل قدرة لكل حساب.
القواعد الواردة هنا تصف خط الأساس المراجع في النسخة الحالية. قبل تفعيل أي
ميزة إنتاجية، نفّذ أولًا GET /api/partner/live/integration بالمفتاح المخصص
للشريك. استخدم GET /api/partner/integration لعقد Test فقط، ثم نفّذ
GET /api/merchant/v1/features بعد إصدار اعتماد التاجر. اعتبر نتيجة الاكتشاف
مصدر القرار النهائي للنسخ والبيئة والصلاحيات والقدرات والحدود.
هذه النسخة تشمل عقد المرشح لإصلاح رحلة العقد واسترجاع عمليات الأسرار. اكتشاف
الشريك في المرشح 1.4.0 وMerchant API 1.5.0؛ يبقى إثبات النشر والقبول
المالي سجلين منفصلين. لا يكفي ملف الدليل لإثبات توفر المسارات على الإنتاج.
1. حدود المسؤوليات
تحتفظ منصة SaaS بما يلي:
- معرفات الربط المحلية في قاعدة بياناتها، مع ربط كل سجل بـ
platform_id,merchant_id,environment, وbeanzpay_id. - المفتاح السري للشريك أو Bearer الخاص بالتاجر داخل Secret Manager فقط.
- نسخة الطلب الأصلي، و
Idempotency-Key، وreferenceقبل كل عملية كتابة. - حالة الطلب المحلية، وسجل محاولات الـ webhook، وآخر نتيجة مؤكدة.
تتولى BeanzPay ما يلي:
- تثبيت حدود التاجر والبيئة والصلاحيات والـ MID داخليًا.
- إنشاء checkout مستضاف أو embedded وفق أصل الموقع المسجل.
- التحقق من التوقيع، وحفظ الحالة المالية، وإنشاء سجل refund فرعي مستقل.
- إدارة Webhooks، وإعادة المحاولة المقيدة، والـ reconciliation الموثق.
- استقبال طلبات التشغيل اليدوية مثل Apple Pay وتغيير وسيلة الدفع.
لا تنقل المنصة إلى متصفح التاجر أي Partner Secret أو Merchant Bearer أو سر
Webhook. ولا تعرض للعميل اسم أو استجابة المزود upstream؛ استخدم رسائل مثل
payment_pending, payment_confirmed, refund_requires_reconciliation.
2. البيئات والعناوين
استخدم متغيرات بيئة قابلة للتغيير بدل تضمين العناوين في الكود:
PARTNER_TEST_BASE_URL = https://<beanzpay-host>/api/partner
PARTNER_LIVE_BASE_URL = https://<beanzpay-host>/api/partner/live
MERCHANT_BASE_URL = https://<beanzpay-host>/api/merchant/v1
في النشر الحالي يكون مضيف BeanzPay هو المضيف المنشور، لكن الـ Connector العام
هو https://beanzpay.com؛ يجب أن يقرأ العنوان من إعداداته وأن يرفض redirects
والعناوين خارج allowlist. تغيير العنوان لا يحول Test إلى Live ولا يغير الاعتماد.
| السطح | الغرض | المصادقة | ملاحظة التفعيل |
|---|---|---|---|
| Partner Test | اكتشاف، onboarding، إدارة بيانات الاختبار، وعمليات مالية Test إن ظهرت كقدرة | X-Partner-Key + X-Partner-Secret |
الدفع Test مشروط بملف Test حقيقي ومفعل؛ وجود المسار وحده لا يكفي |
| Partner production administration | onboarding الإنتاج، claim، التقارير والإدارة | نفس ترويسة الشريك | السطح الإداري الإنتاجي ليس سطح الدفع المالي الإنتاجي |
| Merchant Native Test | الدفع والتقارير والـ webhooks ببيئة التاجر Test | Bearer مربوط بتاجر وبيئة ونطاقات | يحتاج موافقة وإصدار اعتماد وملف Test حقيقي |
| Merchant Native Live | الدفع والـ refund والاستعلامات المقيدة | Bearer Live مربوط بالتاجر والـ MID | يحتاج capability/owner/provider gates مستقلة |
لا تخلط production الخاصة برحلة onboarding مع live الخاصة بالعمليات
المالية. قد يملك التاجر ملفي Test وLive منفصلين.
3. المصادقة والملكية والصلاحيات
Partner S2S
أرسل الترويسات من خادم المنصة فقط:
X-Partner-Key: <partner-key>
X-Partner-Secret: <partner-secret>
Accept: application/json
الترويسات مطلوبة معًا. لا يكفي المفتاح العام وحده، ولا تُحفظ القيم في Postman collection أو frontend bundle أو سجلات الطلبات. اجعل كل بيئة لها Secret Store منفصلًا.
Merchant Native
Authorization: Bearer <merchant-bound-token>
Accept: application/json
الاعتماد يثبت التاجر والبيئة والنطاقات؛ لذلك لا تضف environment في body أو
query لعمليات Native. لا تقبل المنصة معرف تاجر أو MID من العميل لاختيار حساب
آخر. افحص GET /account ثم GET /features وسجل credential.scopes وبيانات
الجاهزية دون تسجيل الاعتماد نفسه. يعيد /features أيضًا data.effective_scopes؛
لا تخلطها باسم scopes داخل كائن اعتماد /account.
النطاقات الحالية القابلة للإصدار هي:
account:read payments:create payments:read
payments:reconcile payments:manage refunds:create
refunds:read refunds:reconcile transactions:read
transfers:read webhooks:read webhooks:manage
invoices:read invoices:manage subscriptions:read
subscriptions:manage card_tokens:read payments:authcap
وجود اسم نطاق مثل payments:authcap في المفردات لا يثبت وجود مسار capture/void عام.
اعتمد قائمة العمليات الفعلية في OpenAPI ونتيجة الجاهزية معًا.
اطلب أقل مجموعة لازمة. أي إضافة نطاق إلى تفويض موجود تحتاج موافقة صريحة جديدة؛ لا توسع grant قديمًا تلقائيًا.
لا يقدم اعتماد Partner الحالي مفاتيح فرعية أو نطاقات موظفين مقيدة بكل تاجر؛
scope_enforcement=not_supported_for_legacy_partner_credentials لا يساوي تفويضًا
ماليًا. تطبق المنصة أدوار موظفيها وعزل المتاجر في خادمها، ولا توزع اعتماد الشريك.
اقرأ GET /merchants/{merchant}/native-credentials لمعرفة scopes,
authorization_id, active, ووقت الانتهاء دون السر، واقرأ
GET /merchants/{merchant}/native-authorizations للتفويض، نسخته، موافقة المالك،
انتهائه وسحبه. expires_at=null في التفويض يعني لا حد زمني له؛ يبقى قابلاً
للسحب ومشروطًا بالملكية والجاهزية. اعتماد Native نفسه مدته الحالية سنة تقويمية (P1Y)،
وبحد خمسة اعتمادات فعالة لكل بيئة وفق الاكتشاف. سحب أو انتهاء أو تدوير اعتماد Native المفوض، أو سحب تفويضه، يمنع استخدامه
ويوقف الإشعارات المرتبطة به؛ لا ينتقل endpoint أو محاولاته تلقائيًا لاعتماد
جديد. endpoints التي أنشأها المالك مباشرة لها إدارة مستقلة؛ افحصها وألغها
صراحة عند إنهاء الربط. لا يفيد ذلك حذف السجل المالي أو إلغاء كل إشعارات المالك.
رحلة المالك
عندما تكون المنصة تريد ربط تاجر موجود أو إصدار اعتماد Native، تكون موافقة المالك والتوقيع وتحديد البيئة والنطاقات لازمة للإصدار المالي، وليست خطوة اختيارية يمكن تخطيها:
- أنشئ السجل أو استخدم
POST /merchants/claimمع claim token أصدره مالك التاجر من رحلة تحقق BeanzPay. claim token لمرة واحدة ومربوط بالشريك والبيئة. - اطلب من المالك الموافقة على التاجر، البيئة، والنطاقات المطلوبة في رحلة owner authorization. لا تضع secret في رابط الاستمرار.
- اقرأ
GET /merchants/{merchant}/native-authorizationsقبل الإصدار. - أصدر الاعتماد عبر
POST /merchants/{merchant}/native-credentialsبعد تحقق owner/provider readiness. يظهر Bearer مرة واحدة فقط في قناة سرية. - عند الدوران استخدم
POST .../{credential}/rotate؛ احفظ الاعتماد الجديد قبل إبطال القديم محليًا، ثم اختبره على route قراءة غير مالية. الاعتماد القديم يفشل بعد الدوران ولا توجد فترة سماح عامة. - عند الإيقاف استخدم DELETE، أوقف الـ Connector، وامسح القيمة من الذاكرة والـ Secret Manager وفق سياسة الاحتفاظ لديك.
استرجاع عمليات إصدار الأسرار
يجب إرسال Idempotency-Key بطول 8–120 من ASCII المرئي دون مسافات عند
POST /merchants/{merchant}/native-credentials وعند الدوران، وكذلك
POST /webhooks وPOST /webhooks/{endpoint}/rotate على Native. احفظ المفتاح
والطلب المعياري والبيئة ومعرف التفويض قبل أول محاولة. هذه عمليات محلية في
قاعدة BeanzPay؛ ليست طلب دفع أو استرداد لدى المزود.
أول استجابة ناجحة فقط تعيد token أو secret ومعها operation. احفظ السر
فورًا في Secret Manager دون logs. مع بقاء صلاحية actor والتفويض وربط المورد، إعادة نفس العملية المكتملة تعيد
409 secret_already_delivered وerrors.operation فقط؛ تغيير body الفعال بعد validation أو نوع
العملية أو المورد أو التفويض بالمفتاح نفسه يعيد 409 idempotency_conflict.
لا تعيد المنصة السر، ولا تنفذ mutation ثانية بهذه الإعادة.
بعد timeout أو انقطاع الرد، استعلم بالمفتاح الأصلي في الرأس:
GET /api/partner/live/merchants/{merchant}/native-credential-operations?environment=live
Idempotency-Key: credential-issue-synthetic-0001
GET {MERCHANT_BASE_URL}/webhook-operations
Idempotency-Key: webhook-create-synthetic-0001
الأول يحتاج اعتماد الشريك والتاجر والبيئة test|live الإلزامية. الثاني يحتاج
Bearer نفسه أو اعتمادًا صالحًا ضمن التفويض نفسه ونطاق webhooks:read؛ البيئة
من Bearer. لا ترسل مفتاح العملية في query. الرد مسطح {operation: {...}}،
وفيه kind, result_type, result_id, status=completed و
secret_retrievable=false. لا يوجد TTL معلن للإيصال ولا pending/failed قابل
للقراءة؛ الحجز وتغيير المورد والإكمال commit ذري واحد. الخطأ قبل commit يعكس
المعاملة المحلية كاملة. لا تجعل 404 record_not_found إذنًا لإصدار بديل تلقائي.
إذا ثبت الإكمال وضاع السر، لا يمكن استعادته من الإيصال. ابدأ إجراء recovery
مقصودًا: اعرف result_id، ثم دوّر المورد المعروف بمفتاح جديد بعد اعتماد
المشغل والتأكد من التفويض والعزل، واحفظ السر البديل. إذا تعذر إثبات النتيجة،
صعّد إلى عمليات BeanzPay. لا تنشئ مفتاحًا جديدًا في كل مزامنة. لا تطبق قاعدة
إعادة الدفع أو الاسترداد على استجابة سر أحادي العرض. العينات في
secret-operations-client.mjs لا تعيد POST تلقائيًا.
حدود صلاحية استرجاع الإيصال
ضمان العملية هو عدم إنشاء mutation ثانية وعدم كشف السر ثانية؛ ليس حق قراءة دائمًا بعد سحب التفويض. قد يرجع الطلب401 أو404 إذا زالت المصادقة أو التفويض أو المورد الذي يُفحص قبل ledger. استعلام العملية يحتاج صلاحية actor والبيئة الحاليتين؛ سحب Native grant قد يمنع Bearer من الاستعلام، وعندها تتبع عمليات BeanzPay أو مسار الشريك المصرح نفسه دون توسيع الصلاحيات.
يُحسب request hash من الحقول الفعالة التي اعتمدها Validator، مع ترتيب مفاتيح objects. ترتيب array مثل events يبقى مهمًا؛ الحقول المجهولة المتجاهلة لا تغيره. نقص أو فساد رأس Idempotency-Key يعيد422 Laravel بصيغة message وerrors؛ أما تعارض العملية وإكمالها و404 فتستخدم type وmessage وerrors المسطحة.
قراءة العقد واستمرار رحلة المالك
تجهيز العقد يخضع لاكتمال المراجعة ورسوم التأسيس وموافقة المزود وفق حالة الطلب. السداد وحده لا يعد عقدًا جاهزًا فورًا؛ تابع الحالة وحدث جاهزية العقد. بعد توقيع المالك يبقى تفويض Native والبيئة والنطاقات وجاهزية الدفع شروطًا مستقلة لإصدار الاعتماد.
اقرأ GET /applications/{application}/contract على Partner Test أو production.
الرد مسطح ويحفظ application_id, merchant_id, environment و
application_status، ثم contract=null قبل التجهيز أو metadata للعقد الحالي:
id, version, status, signed_at, sha256, signed_verified,
signature_method=email_otp, owner_auth_required=true ورابطي عرض وتنزيل المالك.
القراءة لا تجهز عقدًا ولا تصدر إشعارًا. SHA يخص PDF غير الموقع قبل التوقيع ثم
PDF الموقع بعده؛ signed_verified نتيجة تحقق سلامة فعلية، وليس alias لحالة
التاجر أو علامة قديمة.
رابطا owner_view_url وowner_download_url يحتاجان جلسة المالك الصحيحة؛ لا
يمثلان تنزيل PDF مجهولًا أو S2S، ولا تعيد هذه الواجهة snapshot أو بيانات
المالك أو الرسوم. للحصول على رابط استمرار للطلب نفسه أرسل بلا body:
POST {PARTNER_LIVE_BASE_URL}/applications/{application}/owner-continuation
تحفظ العملية application وmerchant وUUID الاستمرار وreturn_url السابق.
الرابط الصالح يحفظ expiry نفسه؛ المنتهي يجدد 48 ساعة. لا تعيد إرسال تسجيل
التاجر لتجديد الرابط، ولا ترسل return_url أو external_reference أو
merchant_id أو package_slug هنا؛ تُرفض هذه الحقول بـ422. الرد يعيد
owner_continuation_url وmetadata وnext_action كإرشاد:
contract_signature, partner_authorization أو merchant_onboarding.
الرابط يفرض OTP وموافقة المالك والبوابات الفعلية، ولا يعد redirect تلقائيًا
إلى المنصة. عدم تطابق الشريك أو البيئة أو الملكية يعيد404.
استقبل contract.ready عند commit تجهيز PDF وcontract.signed عند commit
توقيع موثّق. كل milestone محجوز بصورة دائمة مستقلة عن merchant.activated؛
لا تستخدم ACTIVE أو legacy flags لتوليد توقيع سابق أو backfill. تدعم القناة
التكرار المحتمل بمعرف حدث ثابت؛ احفظ dedupe دائمًا. عند فقد الإشعار اقرأ
metadata العقد وراقب قناة Partner أو اطلب متابعة العمليات؛ القراءة لا تعيد
إرسال الحدث. توقيع العقد لا يفعّل المال ولا يغني عن التفويض والجاهزية.
لا يعد التحديث إرسال أحداث بأثر رجعي لكل عقد موجود قبل النشر؛ استخدم قراءة
العقد الحالي للاستعلام عن العقود السابقة والتعافي من إشعار مفقود.
4. الاكتشاف قبل كل إصدار
ابدأ دائمًا باكتشاف الإنتاج الإداري، ثم استخدم عقد Test عندما يكون المطلوب اختبارًا منفصلًا:
GET {PARTNER_LIVE_BASE_URL}/integration
GET {PARTNER_LIVE_BASE_URL}/packages
GET {PARTNER_LIVE_BASE_URL}/documents/required?merchant_type=company&owners_count=1&signatory=owner&signatory_count=1
# Test only:
GET {PARTNER_TEST_BASE_URL}/integration
سجل نسخة العقد، environment aliases، النطاقات، أنواع الطلبات التشغيلية، حدود body، سياسة retry، ونطاقات الـ SLA. لا تعتبر package catalog سعرًا نهائيًا؛ السعر التجاري quote يلتقطه BeanzPay عند قبول الطلب ولا يعاد تسعيره عند activation.
بعد ربط التاجر:
GET {MERCHANT_BASE_URL}/account
GET {MERCHANT_BASE_URL}/features
الحالة registered تعني أن route موجود. الحالة conditional تعني أن عليك
انتظار profile أو owner consent أو capability. لا تعرض زرًا أو وعدًا تجاريًا
اعتمادًا على وجود route فقط.
5. رحلة onboarding الأساسية S2S
المسار الإنتاجي الأساسي هو POST /api/partner/live/merchants من خادم المنصة.
لا تجعل Hosted Connect هو المسار الافتراضي الآلي؛ يمكن عرضه كمسار بديل عندما
يظهر في الاكتشاف، لكنه لا يستبدل مرجع الطلب أو موافقة المالك.
الحقول الإلزامية الحالية في طلب إنشاء merchant هي:
{
"external_reference": "platform-merchant-00042",
"package_slug": "<allowed-package-from-discovery>",
"merchant_type": "company",
"commercial_registration": "<registration-number-from-secure-form>",
"business_name": "Example Merchant",
"email": "owner@example.test",
"mobile": "05XXXXXXXX",
"website": "https://merchant.example.invalid",
"iban": "SA<22-digits>",
"owner_name": "Verified Owner",
"owner_national_id": "<owner-id-from-secure-form>"
}
المثال هيكلي؛ القيم بين <...> ليست بيانات صالحة للإرسال. للشركة يلزم
commercial_registration، وللعامل الحر freelancer_doc_number وفق عقد النوع.
external_reference ثابت ومفهرس في قاعدة المنصة؛ external_id alias قديم ولا
يُرسل مع قيمة مختلفة. لا تختار package_slug من قائمة محلية؛ استخدم حزمة
مسموحة من GET /packages. أرسل settlement_schedule وحقول النشاط المشروطة
إذا طلبها الاكتشاف. أرسل retail_price_minor فقط إذا كان عقد الشريك يطلب
السعر؛ لا ترسل commercial_pricing_quote أو أي quote محسوب من العميل. BeanzPay
يحفظ snapshot تجاريًا موثوقًا من الخادم عند القبول، ولا يعيد تسعير طلب مقبول
عند تفعيل التاجر.
تعود الاستجابة بسجل application وربما owner_continuation_url وبيانات ربط
ثابتة مثل application_id, merchant_id, وexternal_reference. احفظها قبل
إظهار حالة النجاح. مسار الاستمرار opaque ومؤقت، ويستخدمه المالك الموثق لـ:
- التحقق في بوابة BeanzPay عبر OTP/البريد أو المسار الذي يعيده الخادم؛ لا تقدمه على أنه تحقق حكومي لمجرد وجود اسم داخلي مختلف في metadata.
- توقيع الشروط المعروضة في رحلة المالك.
- الموافقة الصريحة على البيئة وقائمة النطاقات المطلوبة.
يمكن إرسال document_fields اختياريًا ضمن طلب الإنشاء: خريطة مفاتيحها أنواع
المستندات وخاناتها كما تعيدها المتطلبات الديناميكية، مثل company_address
وowner_id وowner_address_2. تُحفظ الحقول المعروفة الجزئية، وتُفحص القيم
المرسلة حسب تعريفها. تُرفض أسماء المستندات والحقول غير المعروفة والخانات التي
تتجاوز عدد الملاك أو المفوضين، وإرسال المفتاح الأساسي و_1 للخانة نفسها.
مثال على جزء البيانات الأولية داخل جسم الإنشاء:
{"document_fields":{"company_address":{"city":"Riyadh","country":"SA"}}}
هذا يحفظ جزءًا من العنوان ولا يعلن اكتماله؛ address العام لا يحل محل بيانات
العنوان الوطني للمنشأة.
تبقى هذه البيانات جزءًا من طلب الإنشاء الأصلي؛ إعادة الطلب بالمرجع نفسه والبيانات نفسها تعيد الطلب القائم، ولا تستخدم تغييرها في إعادة الإنشاء لتعديل طلب سابق. عند رفع ملف للخانة نفسها تُحفظ البيانات الأولية عند غياب نسخة أحدث، ويمكن استكمال بيانات المستند غير المتعلق بالهوية في أول رفع. تتقدم بيانات المستند الأحدث على البيانات الأولية، ولا تُدمج هوية جديدة مع رقم هوية أو تاريخ ميلاد من نسخة قديمة. بيانات مستند يتطلب ملفًا لا تُعد استلامًا للملف، ولا تمثل موافقة المالك أو اعتماد المراجعة أو التفعيل المالي.
بعد ذلك اقرأ حالات وعمليات الرحلة من المسارات الديناميكية التي يعيدها العقد،
ومنها GET /merchants/{merchant}/application,
GET /merchants/{merchant}/environment,
GET /merchants/{merchant}/capabilities, وواجهات
/applications/{application} للوثائق والإرسال والتعديلات والـtimeline والإلغاء.
لا تخترع حالة من اسم الصفحة؛ خزّن status وnext_actions كما يعيدها الخادم.
قراءة GET /applications/{application}/documents تشخيص محلي لا يغيّر الطلب.
تبقى أعلام uploaded وall_required_complete وall_required_uploads_complete
مؤشرات استلام ووجود فقط. يعيد كل مستند أيضًا metadata_complete وmissing_fields
(أسماء الحقول فقط) وmetadata_source وmetadata_receipt_mismatch. ويعيد الملخص
all_required_metadata_complete وmissing_required_metadata حسب نوع/موضع المستند،
وstale_required_document_receipts وreadiness_scope وreview_ready. لا تصبح
review_ready=true إلا باكتمال الملفات المطلوبة وصحة بيانات المستندات
المطلوبة؛ المستندات الاختيارية لا تمنعها. هوية المالك owner_id تتطلب الآن
صورة أمامية فعلية واحدة لكل مالك مع الحقول النصية. تبقى هوية المفوض
signatory_id بلا ملف إذا أعادت المتطلبات requires_upload=false.
بعد الإرسال يمنع owner_identity_files_available=false الجاهزية عند فقد ملف
هوية مالك فعليًا ولو بقي uploaded=true؛ تكون القيمة null قبل الإرسال أو دون تاجر.
قبل الإرسال يكون النطاق application_receipt_snapshot. بعد وجود submitted_at
يكون النطاق:
application_receipts_with_matched_current_metadata
تظهر تصحيحات بيانات
المراجعة الحالية فقط عند تطابق موضع المستند والملف أو إيصال الاستلام ومراجعته.
تغيير مسار الملف يبطل الإيصال القديم ولو نُسخت بيانات إيصال قديم إلى الملف الجديد.
السجل الحالي المفقود أو المستبدل أو الملتبس يمنع الجاهزية ويظهر في قائمة الإيصالات
القديمة. قد تكون missing_fields فارغة عند عدم التطابق لأن الحقول مكتملة لكن
الإيصال لم يعد يمثل السجل الحالي. مصدر البيانات يميّز لقطة الطلب عن السجل الحالي
المطابق أو غير المطابق أو المفقود. لا تعيد القراءة رفع المستندات أو إرسال الطلب
ولا تحوّل review_status=received إلى اعتماد. الجاهزية هنا اكتمال محلي للبيانات،
ولا تعني موافقة المزود أو موافقة المالك أو تفعيل التاجر.
قد يجعل تصحيح بيانات مفوض لا يتطلب ملفًا في السجل الحالي المطابق الطلب جاهزًا
للمراجعة، بينما يبقى عداد الاستلام القديم جزئيًا. أما مالك owner_id فلا تكفي
بيانات هويته وحدها بعد هذا التحديث: يجب وجود الصورة الأمامية المطلوبة أيضًا.
متطلبات المستندات ورفعها
للاكتشاف الأولي أرسل merchant_type=company أو freelancer؛ اسم حقل النوع
في الاستجابة هو account_type، أما account_type=company في query فلا
يحدد نوع الاكتشاف، وحذفه يترك الافتراضي freelancer. للطلب القائم اقرأ دائمًا
GET /applications/{application}/documents؛ فهو المرجع لخانات هذا الطلب
وعدد الملاك والمفوضين وrequires_upload وحقوله الفعلية.
ارفع الملف والبيانات معًا إلى POST /applications/{application}/documents.
الحقول type وfile مطلوبة؛ fields مصفوفة Multipart بمفاتيح محاطة بأقواس،
وليست نص JSON. يقبل الملف PDF أو JPEG أو PNG حتى 2048 KiB، مع فحص محتواه
الفعلي؛ تغيير الامتداد لا يجعل الملف صالحًا. قيم حقول رفع Multipart نصوص بحد
255 حرفًا، مع قواعد الحقل الأدق. تختلف هذه الحدود عن حدود document_fields
في طلب الإنشاء. دع cURL يحدد Content-Type وboundary تلقائيًا.
أمثلة تركيب فقط ببيانات وهمية: صدّر المتغيرات من مخزن آمن، واستبدل المسارات
بملفات مصرح بها وقيم البيانات الفعلية. لم تُنفذ هذه الأوامر على تاجر حقيقي.
PARTNER_LIVE_BASE_URL=https://beanzpay.com/api/partner/live وAPPLICATION_ID
هما إعدادا خادم المنصة؛ الآيبان أدناه مثال تنسيقي ولا يثبت ملكية حساب.
curl --fail-with-body --request POST \
"$PARTNER_LIVE_BASE_URL/applications/$APPLICATION_ID/documents" \
--header "X-Partner-Key: $PARTNER_KEY" \
--header "X-Partner-Secret: $PARTNER_SECRET" \
--header 'Accept: application/json' \
--form 'type=bank_iban_certificate' \
--form 'file=@/secure/example-bank.pdf;type=application/pdf' \
--form 'fields[holder]=Example Merchant' \
--form 'fields[iban]=SA1000000000000000000000'
curl --fail-with-body --request POST \
"$PARTNER_LIVE_BASE_URL/applications/$APPLICATION_ID/documents" \
--header "X-Partner-Key: $PARTNER_KEY" \
--header "X-Partner-Secret: $PARTNER_SECRET" \
--header 'Accept: application/json' \
--form 'type=owner_address' \
--form 'file=@/secure/example-address.pdf;type=application/pdf' \
--form 'fields[name]=Example Owner' \
--form 'fields[street]=Example Street' \
--form 'fields[district]=Example District' \
--form 'fields[building_number]=1234' \
--form 'fields[postal_code]=12345' \
--form 'fields[city]=Riyadh' \
--form 'fields[country]=SA'
secondary_number اختياري في العنوان؛ استخدم اسم slot المكتشف مثل
owner_address_2 عند تعدد الملاك. حقول شهادة الضريبة تشمل number، وحقول
السجل التجاري تشمل number كما يعيدها المخطط. لا يستخرج رفع
الصورة بياناتها تلقائيًا، ولا تترك النصوص فارغة اعتمادًا على وجود الملف.
لحفظ الحقول النصية لهوية المالك أو المفوض استخدم JSON دون ملف في طلب PATCH نفسه:
PATCH {PARTNER_LIVE_BASE_URL}/applications/{application}/documents/owner_id/fields
Content-Type: application/json
{"fields":{"id":"1234567890","mobile":"0501234567","date_of_birth":"1990-01-01"}}
هذه بيانات وهمية؛ الهوية والجوال والتاريخ مطلوبة مع عمر لا يقل عن 18 عامًا.
PATCH يحفظ النصوص ولا يرفع صورة. من Partner API 1.9.0 تحتاج كل خانة
owner_id إلى صورة أمامية فعلية واحدة ويظهر requires_upload=true. لا يوجد
حقل صورة خلفية إضافي في عقد الشريك الحالي. عند تعدد الملاك استخدم الخانة
المكتشفة مثل owner_id_2 ولا تعِد استخدام صورة مالك لمالك آخر.
curl --fail-with-body --silent --show-error \
--url "$PARTNER_LIVE_BASE_URL/applications/$APPLICATION_ID/documents" \
--header "X-Partner-Key: $PARTNER_KEY" \
--header "X-Partner-Secret: $PARTNER_SECRET" \
--header 'Accept: application/json' \
--form-string 'type=owner_id' \
--form 'file=@/secure/synthetic-owner-front.jpg;type=image/jpeg' \
--form-string 'fields[id]=1234567890' \
--form-string 'fields[mobile]=0501234567' \
--form-string 'fields[date_of_birth]=1990-01-01'
هذا قالب ببيانات وهمية، وليس إثبات رفع Live. حد ملف Partner هو 2048 KiB
بصيغ PDF أو JPEG أو PNG مع فحص المحتوى. يضبط cURL حد Multipart؛ لا تضبطه
يدويًا ولا ترسل fields كنص JSON. لا يستخرج رفع الصورة النصوص تلقائيًا.
هوية المفوض تبقى حقولًا فقط متى أعاد العقد requires_upload=false. لا يغيّر
PATCH حقول العنوان أو البنك. الرفع وPATCH متاحان حسب حالة الطلب، ولا يتطلب
الرفع موافقة Native مسبقًا. يوجد استثناء محدد لاستكمال صورة مالك مطلوبة
ومفقودة في طلب مدفوع قائم: يلزم بقاء الطلب في المعالجة بعد السداد، وربطه
بالتاجر والشريك الصحيحين، ووجود حساب المزود ومرجع السداد الأصلي، وأن تكون
حالة التاجر ضمن نافذة الإصلاح المسموحة. يسمح الاستثناء برفع الصورة المفرد
أو الدفعي وبـ PATCH لنصوص الخانة المفقودة فقط؛ لا يفتح تعديل بقية المستندات
أو حذفها. يجب اكتمال النصوص قبل تخزين الصورة، ويمكن إبقاء الحقول الحالية
الكاملة عند عدم إرسال حقول أحدث. بوابة مستندات المالك تتيح الاستكمال أيضًا.
بعد اكتمال كل الملفات والبيانات المطلوبة تعود المستندات إلى مراجعة BeanzPay
المحلية تلقائيًا ويعيد الرفع application_status=under_review عند الانتقال؛
لا تعِد submit. يحدث ذلك
دون إعادة التسجيل أو السداد أو تغيير معرفات الربط. لا يرسل الرفع وحده الطلب
إلى المزود، ولا يلغي اعتماد المستندات الأخرى؛ تراجع الإدارة الصورة الجديدة
قبل استكمال الربط. تبقى بقية تعديلات الطلب المدفوع مقيدة بحالتها. إذا بقيت صورة مالك آخر أو
بيانات مطلوبة ناقصة، فلا يعني استلام ملف واحد اكتمال الانتقال للمراجعة.
أعد قراءة GET /applications/{application}/documents وحالة الطلب بعد الرفع.
نجاح الرفع هو إيصال استلام، ولا يعني اعتماد الصورة أو الموافقة المالية.
إذا أعادت الحالة 400 أو الحقول 422، تابع الطلب نفسه ولا تنشئ طلبًا بديلًا.
تجديد استكمال المالك والشروط
رابط المالك صالح حاليًا 48 ساعة؛ انتهاؤه يعيد 410 Gone. لتجديده أرسل
POST /applications/{application}/owner-continuation بلا body باستخدام معرف
الطلب الحالي. يتجدد المنتهي فقط مع نفس UUID ومعرفي الطلب والتاجر وreturn_url
المحفوظ؛ الصالح لا يمدد. لا تعِد إنشاء التاجر ولا ترسل بياناته أو return_url
في طلب التجديد. تابع العقد الحالي وحالة الطلب عبر القراءة وPartner lifecycle؛
إعادة التوجيه التلقائي إلى المنصة ليست وعدًا في هذا العقد.
شروط التاجر الرسمية: https://beanzpay.com/terms، والنسخة الثابتة الحالية
https://beanzpay.com/terms/1.0 المنشورة والنافذة في 1 سبتمبر 2026. احتفظ
بالنسخة الثابتة المعروضة عند الموافقة. تحفظ BeanzPay نسخة العقد وبصمته ودليل
توقيع email OTP؛ لا يوجد في طلب إنشاء التاجر حقل عام terms_accepted يحل
محله. مسار /portal/nafath الحالي اسم داخلي لتوقيع email OTP، وليس نفاذًا
حكوميًا. شروط التاجر، وتفويض Native، والاتفاق التجاري للشريك ثلاث موافقات
مختلفة؛ لا تستخدم مسودة اتفاق شريك بدل شروط التاجر.
Claim لتاجر موجود
عند ربط حساب قائم، يصدر مالك الحساب claim token أحادي الاستخدام من رحلة المالك، ثم يرسل الخادم:
POST {PARTNER_LIVE_BASE_URL}/merchants/claim
X-Partner-Key: <partner-key>
X-Partner-Secret: <partner-secret>
Idempotency-Key: claim-merchant-00042
Content-Type: application/json
{"claim_token":"<owner-issued-token>","external_reference":"platform-merchant-00042"}
الـclaim مربوط بالشريك والبيئة ولا ينشئ merchant جديدًا أو اعتمادًا أو رسومًا أو عمولة بأثر رجعي. إذا ضاعت الاستجابة، أعد القراءة أو أعد نفس المفتاح والجسم؛ لا تنشئ claim ثانيًا.
6. مساران لبناء الـ Connector
المسار الأساسي S2S
هذا هو المسار الموصى به لمنصة SaaS:
- خزّن
platform_merchant_idواطلب onboarding أو claim. - انتظر readiness وeffective capability.
- أنشئ payment من backend المنصة باستخدام Bearer الصحيح.
- أرسل العميل إلى
checkout_urlأو استخدمembed_urlعند قبول embedded origin. لا تجمع raw card data. - استقبل Native webhook، تحقق من التوقيع والهوية، ثم اقرأ payment محليًا.
- نفذ refund كـ child operation واحفظ حالة الأب والابن.
مسار المالك/التاجر الموجود
يصلح عندما يكون التاجر قد بدأ حسابه خارج المنصة. استخدم claim بالـ external
reference الثابت، ولا تنشئ سجلًا جديدًا لمجرد أن الاستجابة ضاعت. الـ claim
يحفظ التاريخ ولا ينشئ setup fee أو عمولة بأثر رجعي.
7. الدفع والـ checkout
إنشاء Payment
POST /payments يحتاج Idempotency-Key من ASCII المرئي (0x21–0x7e، بلا
مسافات) بطول 8–120.
الجسم الأساسي:
{
"amount": 12500,
"currency": "SAR",
"reference": "order-2026-00042",
"customer": {
"first_name": "Customer",
"last_name": "Example",
"email": "customer@example.test",
"phone_number": "+966500000000"
},
"expires_in": 1800
}
amount وحدة صغرى صحيحة وليست decimal. لا تحول المبلغ مرتين ولا تعتمد على
قيمة قادمة من browser. reference ثابت لكل order؛ لا تنشئ reference جديدًا
بعد timeout. إذا أضفت checkout فـ origin يطابق أصل موقع التاجر المسجل
حرفيًا، وreturn_url HTTPS على نفس الأصل ومسار بلا query أو fragment أو
userinfo أو منفذ مخصص. locale هو ar أو en.
الـ response يعطي BeanzPay UUID محليًا، checkout_url وربما embed_url،
الحالة، العملة، المبلغ، المرجع، وقت الانتهاء، وmetadata آمنة. لا يوجد provider
ID أو card secret في response.
فقدان الاستجابة
201/200مع سجل معروف: احفظpayment.idوreference.409 payment_outcome_unknownأو503 payment_outcome_unknown: لا ترسل POST جديدًا. أعد نفس body مع نفسIdempotency-Keyأو استخدم query بالمرجع إن كانت الحالة تسمح.422 payment_rejected: لم يُثبت إنشاء الدفع. صحح السبب ثم استخدم key جديدًا لمحاولة جديدة، ولا تعيد استخدام reference لطلب مختلف.GET /paymentsوGET /payments/{payment}يقرآن آخر حالة محلية مؤكدة؛ لا يفترضان inquiry فوريًا.
8. refund: كامل، جزئي، مرجع الابن، والحالة المجهولة
استخدم:
POST /payments/{payment}/refund
Idempotency-Key: refund-key-2026-0001
لـ full refund احذف amount ليحل الخادم remaining refundable amount. للجزئي
أرسل integer أصغر من أو يساوي المتبقي. يمكن إرسال reason وrefund_reference؛
reference هو alias قديم لنفس child reference. إذا أُرسلا معًا يجب أن يتطابقا
وإلا يكون الرد 422 قبل أي provider submission. نفس الـ aliasين ينتجان نفس
fingerprint، ولا يلغيان child operation قديمًا.
الـ child response يحتوي amount, status, cumulative_refunded_amount,
remaining_refundable_amount, reference, payment_reference, وfees.
لا تعدّ العملية ناجحة إلا عند status=refunded. الحالات الأخرى تعني:
| الحالة | معناها | تصرف الـ Connector |
|---|---|---|
submitting |
حجز محلي قبل/أثناء التقديم | اقرأ نفس السجل ولا تنشئ child آخر |
pending_callback |
نُفذ الطلب وينتظر دليلًا موثقًا | انتظر webhook أو inquiry المسموح |
requires_reconciliation |
لا يوجد دليل نهائي بعد | استخدم reconcile المخصص أو إجراء المشغل |
refunded |
تأكيد نهائي للعملية | حدّث الرصيد بالـ child amount |
failed |
رفض نهائي محفوظ | لا تعيد نفس العملية بـ key جديد إلا بعد إصلاح السبب |
إذا أصبح outcome unknown:
- استخدم
GET /refunds?idempotency_key=<same-key>لاسترجاع child. - أو استخدم
GET /refunds?refund_reference=<child-ref>إن كان محفوظًا. - استخدم
payment_referenceللبحث عن الأب، مع الانتباه أن query aliasreferenceالقديم في GET refunds يعني مرجع الأب لا child. POST /payments/{payment}/reconcileلا يثبت نتيجة child refund وحده. استخدمPOST /refunds/{refund}/reconcileبصلاحيةrefunds:reconcileوجسم JSON فارغ{}للتحقق من الابن نفسه؛ هذا استعلام لا يعيد إرسال الاسترداد.- لا تعيد POST بمفتاح جديد، ولا تعتبر timeout نجاحًا أو فشلًا.
تجري المطابقة الآلية كل خمس دقائق بحد 50 عملية في الدورة، للعمليات المربوطة
بمعرف مزود معروف وعند جاهزية Live/inquiry؛ لا تشمل كل نتيجة مجهولة بلا ربط.
اقرأ provider_operation_bound, last_provider_verified_at,
last_provider_verification_source، وفي رد reconcile checked_at,
requires_operator_review, support_reference, next_action. غياب الربط
أو الدليل النهائي يستلزم عمليات BeanzPay بنفس معرف الابن، دون استرداد بديل.
المثال الكامل للإنشاء والقراءة والبحث والنتيجة غير المحسومة موجود في
examples/refund-recovery-fixture.json داخل الحزمة.
الخادم يحجز المبلغ تزامنيًا ويمنع تجاوز إجمالي المبلغ القابل للاسترداد؛ لا
تحسب المنصة المتبقي من قراءة قديمة ثم تفترض قبول استردادين متزامنين. رسوم
platform_recorded_minor قيود محلية، وprovider_actual_confirmed=false لا
يثبت الرسوم الفعلية. مدد الاسترداد ورسومه تعتمد على حساب التاجر وطريقته
وعقده؛ لا تعرض مدة أو صفر رسوم لمجرد قبول المخطط للمبلغ.
9. Webhooks والتوقيع وإعادة المحاولة
Native financial webhook contract 3.0.0
قبل قبول HTTP 2xx:
- اقرأ raw request bytes دون إعادة serialization.
- تحقق من
X-BeanzPay-Timestampوأن محاولة التوقيع ضمن نافذة 300 ثانية. - احسب
HMAC-SHA256(header_timestamp + "." + raw_body, endpoint_secret). - تحقق أن
X-BeanzPay-Webhook-ID = X-BeanzPay-Event-ID = body.event_id. - تحقق أن
body.timestamp = body.occurred_atحرفيًا؛ timestamp الخاص بالمحاولة قد يختلف عن وقت الحدث. - طابق merchant، environment، reference، amount، currency، وrefund child مع
سجلك المحلي. في partial refund يكون
data.amountمبلغ الابن، لا مبلغ الأب. - خزّن
event_idقبل الرد 2xx. التكرار يعاد إلى نتيجة المعالجة نفسها.
الرؤوس اللازمة هي X-BeanzPay-Signature, X-BeanzPay-Timestamp,
X-BeanzPay-Event, X-BeanzPay-Webhook-ID, X-BeanzPay-Event-ID,
X-BeanzPay-Contract-Version, وX-BeanzPay-Environment.
Native endpoint management
GET /webhooks: metadata فقط، بلا secret أو body.POST /webhooks:endpoint_urlHTTPS عام وeventsمنpayment.paid,payment.failed,payment.refunded,payment.voided؛ يعاد secret مرة واحدة.POST /webhooks/{endpoint}/rotate: secret جديد، والقديم يفشل فورًا.DELETE /webhooks/{endpoint}: إلغاء الربط.GET /webhooks/deliveries: metadata آمنة، وGET .../{delivery}لمحاولات الإرسال دون raw body أو secret.POST .../{delivery}/replay: سبب نصي، key جديد خاص بالـ replay، حد أقصى ثلاث محاولات يدوية مع cooldown 30 ثانية. الإرسال الآلي خمس محاولات بمهلة 30 ثانية وbackoff baseline: 60، 300، 900، 3600 ثانية.
يتاح endpoint نشط واحد لكل تاجر وبيئة. POST /webhooks بمفتاح جديد حتى للعنوان نفسه
يستبدل السجل والسر السابقين، فلا تشغله في كل مزامنة. لا توجد مدة انتهاء
مستقلة معلنة للسر؛ يبقى صالحًا حتى الاستبدال أو التدوير أو الإلغاء، بلا مهلة
سماح للقديم. المحاولات المعلقة لا تنتقل تلقائيًا للربط الجديد. احفظ سجل
الدفعات غير المحسومة وأعد قراءتها، ثم استخدم replay المقيد عند صلاحية الربط.
سجلات Native المالية مصنفة financial_evidence ومحمية من الحذف العام؛ لا
توجد مدة أيام ثابتة مفروضة عند إنشائها في هذا الخط الأساسي. لا تطبق سياسة
تنظيف سجلات Partner عليها. الأمثلة الموقعة الاصطناعية لجميع الأحداث الأربعة
تولد وتتحقق محليًا في native-webhook-verifier.test.mjs، وليست إيصالات مالية.
Partner lifecycle webhook
استخدم GET/PUT/DELETE /webhook وGET /webhook/events وGET /webhook/deliveries على Partner. الوجهة HTTPS عامة ولا تُقبل redirects أو
عناوين private/reserved. لا تعرض هذه routes raw payload أو signature أو response
body؛ replay إجراء مشغل وليس route عامًا. ويمكن استعمال POST /payments/verify-signature للتحقق من payload/timestamp/signature وفق العقد.
قناة lifecycle_v1 تتعلق بدورة التاجر والكتالوج؛ لا تستخدمها لتأكيد مال
العميل. وجود payment/refund في enum تاريخي لا يجعله حدث الحساب الحالي؛ اقرأ
GET /webhook/events؛ تتضمن قناة المرشح contract.ready وcontract.signed
المستقلين عن التفعيل، وعيناتهما الموقعة الخام في partner-contract-events-fixtures.json. أحداث application.* لرسوم التأسيس مستقلة عن Native.
10. Native management والاشتراكات
إدارة الحساب والـ features
GET /account وGET /features للقراءة. لا يعيدان provider credentials أو
integration IDs أو card tokens. اجعل واجهة منصتك تعرض readiness، scope، والبيئة
فقط.
Plans وenrollment
الـ handles المحلية UUID: plan, enrollment, subscription, card,
operation. إنشاء الخطة وتحديثها وإيقافها واستئنافها يحتاج
subscriptions:manage; القراءة تحتاج subscriptions:read.
إنشاء enrollment هو first-3DS flow:
POST /subscription-plans/{plan}/enrollments
Idempotency-Key: subscription-enroll-2026-0001
يحفظ الخادم الطلب، ويعيد checkout محليًا. بعد اكتمال 3DS لا تعتمد على callback
وحده؛ استخدم GET /subscription-enrollments/{enrollment} أو reconcile الفارغ
بعد أن يثبت inquiry الموثق المبلغ والعملة والبيئة، ويربط exactly one
subscription whose initial_transaction matches. لا يقبل caller provider ID.
GET /subscription-operations?idempotency_key=<same-key> recovery محلي بعد
فقدان الاستجابة. الحالة processing أو unknown ليست نجاحًا، ولا ترسل POST
بديلًا.
تحتاج الاشتراكات ملف التاجر والبيئة ومفتاح الربط المطلوب وتفعيل MOTO و3DS ودليل الجاهزية. لا يعني وجود routes إتاحتها لكل حساب. دعم callback lifecycle الكامل ليس مكتملًا في هذا الخط الأساسي؛ يعتمد تأكيد الالتحاق على inquiry الموثق المملوك. هذه اشتراكات تحصيل من عميل التاجر، وليست اشتراك المزود للتاجر أو API دفع عام بأي card token.
إدارة subscription وcards
GET/PUT /subscriptions/{subscription}للقراءة والتحديث.POST /subscriptions/{subscription}/{action}حيث action =suspend,resume, أوcancel.GET .../transactionsيعيد تاريخًا آمنًا.GET .../cardsيحتاجsubscriptions:readوcard_tokens:readويعيد UUID محليًا وmetadata masked فقط.POST .../cards/{card}/{action}حيث action =deleteأوprimary، ولا يتعامل Connector مع raw token أو provider card ID.
11. الفواتير الخاصة بالعميل
POST /customer-invoices ينشئ مستند دفع branded مربوطًا بـ Native Hosted
intention. الحقول الأساسية amount, currency, وcustomer; items اختياري
لكن مجموع line items يجب أن يساوي amount تمامًا. المستند ليس tax invoice ولا
provider fee invoice.
استخدم:
GET /customer-invoicesمعstatusأوidempotency_key.GET /customer-invoices/{invoice}و.../document.POST .../sendمرة واحدة مع business contact الموجود في حساب التاجر.POST .../cancelفقط للفواتير غير المدفوعة محليًا.
حالة payment_unknown لا تعني paid. حفظ key قبل create/send/cancel يتيح replay
آمنًا بلا إرسال بريد أو payment POST ثانٍ.
إرسال الفاتورة يتم من مرسل BeanzPay المعتمد؛ contact التاجر يظهر في النص
ولا يضمن Reply-To. أرسل {} إلى .../send مع المفتاح المحفوظ. الإلغاء يوقف
checkout محليًا فقط، ولا ينفذ refund أو void، والدفع المؤكد المتأخر يتقدم على
الإلغاء المحلي. الفاتورة الضريبية يصدرها نظام التاجر. يمكن مشاركة رابط السداد
أو QR دون إرسال مفاتيح مالية إلى المتصفح.
12. العمليات اليدوية وApple Pay
ينشئ Partner request سجلًا محليًا وmetadata outbox فقط:
{
"request_type": "apple_pay_activation",
"service_key": "apple_pay",
"action": "activate",
"reason": "Activate the registered merchant domain",
"details": {"domain":"shop.example.com"}
}
الأنواع الحالية: apple_pay_activation, payment_method_change,
provider_account_lifecycle, capability_activation, fee_change,
support_ticket. استخدم Idempotency-Key بطول 8–120؛ نفس body/key يعيد نفس
الطلب، وتغيير body مع نفس key يعيد 409.
المسار الكامل: POST /merchants/{merchant}/operational-requests في Partner
Live؛ صلاحية الشريك الحالية هي الحساب المصادق عليه وعلاقة التاجر، وليست
نطاقات Native. action لـ Apple Pay هو activate أو verify_installation
وdetails.domain مطلوب. مثال التغيير: payment_method_change مع
service_key من قاموس الطرق وaction=enable|disable. مثال حساب المزود:
provider_account_lifecycle, service_key=account, action=disable|reactivate.
لـ Apple Pay:
- ترفع المنصة الطلب للتاجر والنطاق والبيئة، وتحفظ معرفه.
- تراجع عمليات BeanzPay الطلب وتتواصل يدويًا مع المزود، وتسجل نتيجة مؤكدة؛ لا يرسل إنشاء الطلب بريدًا إلى المزود تلقائيًا.
- تقرأ المنصة
.../{request}/apple-pay/artifactوتنزل.../{request}/apple-pay/artifact/downloadبالمصادقة، وتطابق SHA-256. - تثبت الملف تلقائيًا على نطاق التاجر المعني في
/.well-known/apple-developer-merchantid-domain-associationعبر HTTPS عام دون تسجيل دخول أو redirect، ثم ترسلPOST .../{request}/apple-pay/installationبالـhash المطلوب ومفتاح مستقل. - تقرأ
provider_stateوeffective_stateوالجاهزية لكل حساب ونطاق. تثبيت الملف أو تطابقه لا يثبت موافقة المزود ولا يفعّل جميع التجار دفعة واحدة.
الهدف التشغيلي هو إنهاء التنفيذ بالكامل خلال ثلاثة أيام عمل، وفق تقويم
الرياض (الأحد–الخميس، مع استبعاد العطلات المضبوطة). في التنفيذ الحالي يبدأ الحساب من
requested_at عند إنشاء السجل، ويحفظ تقويم الطلب وdue_at؛ لا تنتظر المنصة
قبولًا لاحقًا لبدء ساعة جديدة. اعرض الموعد العائد من الخادم وoverdue، وصعّد
التأخر دون اعتماد تلقائي. هذا هدف عمليات BeanzPay وليس ضمانًا زمنيًا من المزود.
احفظ sla.configured, business_days, calendar, due_at, overdue,
late_finished, escalation_required؛ provider_sla_guarantee=false ما لم
يقرر عقد موثق خلافه.
تأكيد المزود وحده لا يجعل قدرة بلا adapter متاحة ماليًا؛ قد تظل
api_effective=false. لتوقيف إنشاء مدفوعات جديدة محليًا فورًا توجد قراءة
controls/payment-creation وPOST إلى .../suspend أو .../resume مع السبب.
هذا منفصل عن طلب تعطيل حساب المزود ذي المهلة التشغيلية، ولا يلغي تلقائيًا
روابط قديمة أو اشتراكات أو دفعات قائمة. التذاكر عبر .../{request}/ticket
وردودها مسار متابعة محفوظ، وليست تنفيذًا ماليًا بديلًا.
13. التسعير التجاري والكتالوج
كل منصة لها عقد تجاري منفصل. لا تنسخ نسبة أو waiver من منصة أخرى، ولا تضع أي نسبة ثابتة أو سياسة خصم خاصة بشريك بعينه في الـ Connector.
القواعد العامة:
تفاصيل سياسة الحصة من الأساس وإعفاء تحمل الخصم ودفتر الحصص أدناه مشروطة بأن يعلنها عقد الشريك واكتشافه. بعض الحسابات تعمل بعقد تجاري سابق؛ لا تحولها لهذه السياسة أو تعرض لها خيار الإعفاء دون تفعيل صريح من BeanzPay.
- الكتالوج المركزي يعرض الباقات والسعر الحالي والـ quote metadata.
- quote الذي يوافق عليه التاجر immutable؛ تحديث catalog لا يعيد تسعير طلب قديم.
- أي discount أو subsidy أو waiver يجب أن يأتي من quote صادر من الخادم مع basis، revision، effective time، وaudit metadata.
- لا يتجاوز سعر البيع سعر الباقة الأساسي المسموح؛ الحصة وأساسها قبل الضريبة والإعفاءات تقرأ من سياسة الشريك والـquote، لا من نسبة ثابتة في الكود.
- عند سياسة حصة BeanzPay الثابتة من الأساس يكون صافي الشريك سعر البيع ناقص الحصة، وقد يصبح سالبًا. عند إعفاء تحمل الخصم المعتمد تكون نسبة الشريك من سعر البيع الفعلي؛ مثال تعليمي بنسبة 25% وسعر بيع 50 يعطي 12.50. المثال لا يمنح الإعفاء أو النسبة لأي حساب؛ تحقق من السياسة الفعلية في الـquote.
- استرجاع setup fee الإداري ليس refund لدفعة عميل ولا route يمكن للشريك تشغيله. عند رد رسوم التأسيس كاملة وتأكيده، تعكس الحصص فلا تبقى حصة لأي طرف؛ لا تعكسها لنتيجة مجهولة أو لمجرد طلب استرداد أو تعطيل حساب.
- عندما تكون سياسة دفتر الحصص مفعلة، يصدر كشفها في اليوم السابع الميلادي؛
هذا موعد النظام الحالي وليس خيار يوم قابلًا للتغيير من الموصل.
تسجيل
paidتوثيق للمطابقة ولا ينفذ تحويلًا بنكيًا. الاعتراض والتصحيح وإثبات السداد وفق الاتفاق التشغيلي والتجاري، بلا سحب آلي من أموال التجار. - حساب المنصة الذاتي كتاجر مستقل عن حساب إدارة الشريك. الإعفاء من التأسيس أو الاشتراك واستبعاده من دفتر الحصص يتطلب إعدادًا معتمدًا، ولا يعفي رسوم المعاملات والاسترداد تلقائيًا.
- statement التجاري يوضح base/tax/retail/discount/benefit وcredit/debit حسب العقد، لكنه ليس رصيد بنك أو settlement أو payout أو tax invoice.
إذا احتاجت منصتك سعرًا خاصًا، اطلب عقدًا تجاريًا جديدًا ولا تستنتج السعر من
GET /packages وحده.
حدود التكافؤ مع أي بوابة أخرى
| المتطلب | ما يتيحه العقد الحالي |
|---|---|
| قائمة/تفاصيل/QR/إلغاء رابط الدفع | متاحة في Native مع النطاق والجاهزية؛ الإلغاء محلي والدفع المتأخر يعالج ماليًا |
| عمليات مالية وفلاتر وتصدير وmetadata | اقرأ /payments و/transactions ومسارات التصدير وmetadata في OpenAPI؛ ليست فلاتر Partner Test دليلًا على Native Live |
| رصيد متاح أو معلق وتسوية بنكية رسمية | غير متاحة بالعقد الحالي؛ تقارير المعاملات والكشف التجاري لا تعوضها |
| إنشاء payout أو إدارة البنوك أو تحويل جماعي | غير متاحة؛ /transfers قراءة فقط ولا يثبت الرصيد المتاح |
| capture/void عام أو دفع عام بالتوكن | غير متاحة كعمليات عامة؛ لا تستبدلها بـrefund |
| تحويل بين التجار أو اشتراك المزود للتاجر | خارج العقد الحالي ويحتاج منتجًا وعقدًا مستقلين |
| كوبونات وخصومات دفعات العملاء | منفصلة عن خصومات رسوم الباقات وغير متاحة ضمنها |
أي بديل يدوي للرصيد أو التسوية يحتاج نموذج تقرير ومصدرًا ودورية ومعرفات ربط
واعتماد الطرفين؛ لا تعد به بلا إجراء قائم. تحفظ CAPABILITY_MATRIX.json
حالة كل قدرة، والبيئة والصلاحيات والقيود. وتحفظ IMPROVEMENT_ROADMAP.md
ما يحتاج تطويرًا ومسؤول تنفيذه واختبار إغلاقه دون اختلاق موعد التزام.
14. الأخطاء والحدود والـ retries
مثال خطأ عمليات Native المالية:
{
"error": {"code": "payment_outcome_unknown", "message": "Safe generic message"},
"errors": ["field_name"]
}
errors أسماء حقول فقط؛ لا تعكس القيم المدخلة. احفظ X-Request-ID للربط في
الدعم. المعاني العملية:
| HTTP | الاستخدام |
|---|---|
| 401 | credential مفقود أو غير صالح |
| 403 | scope/owner/capability غير كافٍ |
| 404 | resource خارج tenant/environment أو غير موجود |
| 409 | conflict أو unknown outcome أو replay مختلف |
| 413 | body أكبر من الحد |
| 422 | validation أو definitive rejection؛ أصلح الطلب قبل retry |
| 429 | rate limit؛ احترم سياسة الرد ولا تغير idempotency key |
| 503 | unavailable أو inquiry/outcome unknown؛ اقرأ error.code |
حدود baseline المهمة: Idempotency-Key بين 8 و120 printable ASCII؛ payment body حتى 64 KiB؛ refund body حتى 16 KiB؛ pagination Native حتى 50، operational requests حتى 100؛ replay اليدوي ثلاث مرات، والآلي خمس مرات. راجع discovery لأن الحدود قابلة للتكوين.
لا تستخدم retry عام لكل 5xx. القرار يكون:
- تحقق من أن العملية لم تكن definitive rejection.
- إن كانت write unknown، أعد نفس body/key أو اقرأ resource.
- إن كانت rate limit، انتظر server policy.
- إن كانت manual/unsupported، أوقف العملية وافتح operational request.
وبالنسبة إلى أي POST مالي، فإن انقطاع النقل أو timeout أو 408 أو 429 أو
أي 5xx، وكذلك رد 2xx ناقص أو غير مطابق للمخطط، يبقى unknown ما لم تثبت
قراءة موثوقة النتيجة النهائية. احفظ نفس المفتاح وbody الأصليين، واسترجع بنفس
المفتاح أو بقراءة مقيدة بالنطاق؛ لا تنشئ key أو reference أو عملية بديلة
تلقائيًا. يسمح بمحاولة جديدة فقط بعد إثبات أن رد 4xx الموثق رفض الطلب نهائيًا.
15. سجل الحالة والقبول
لكل عملية يجب أن تسجل المنصة:
connector_request_id
beanzpay_request_id / resource_id
merchant_id + environment
idempotency_key_hash (لا القيمة في log)
body_fingerprint
reference / refund_reference
created_at, last_checked_at
status, error.code, X-Request-ID
webhook event_id and verification result
قائمة قبول قبل الإنتاج:
- discovery محفوظ ويطابق الإصدار والـ scopes والـ limits.
- Test وLive secrets منفصلان وموصولان بالتاجر الصحيح.
- كل POST يحفظ key/body قبل الإرسال.
- في العمليات المالية duplicate same key/body يعيد نفس المورد؛ conflict body يعطي 409.
- timeout لا ينشئ POST ثانياً.
- full/partial refund يحفظ parent/child وcumulative/remaining.
- unknown refund يملك recovery بالـ key وبـ refund_reference.
- webhook يثبت raw bytes وHMAC والهوية والوقت وdedupe قبل 2xx.
- rotation/revocation يوقف old secret والدفع غير المصرح.
- لا provider ID أو card token أو secret يعبر frontend أو logs.
- Apple Pay request لا يدعي التسجيل التلقائي.
- الأسعار والـ waiver تأتي من quote/contract، بلا hardcode.
- real merchant Test UAT موثق منفصلًا؛ لا يكفي نجاح الاختبارات المحلية.
16. الدعم والتشغيل
عند فتح تذكرة دعم أرسل connector_request_id, X-Request-ID, resource UUID،
environment، version، وerror.code فقط. لا ترسل Authorization، Partner Secret،
Webhook secret، raw payload، card data، أو ملف البيئة.
للحالات المالية المجهولة اذكر هل لديك payment.id أو refund.id أو المرجع أو
الـ idempotency key hash، ثم استخدم query/read-only recovery قبل أي محاولة جديدة.
للحالات التشغيلية اذكر operational_request.id, request_type, الحالة، ووقت
آخر تغيير. لا تحاول تغيير capability أو إنشاء اعتماد بديل من واجهة العميل.
17. فجوات يجب حسمها مع BeanzPay قبل البناء النهائي
- حساب Test حقيقي مستقل لكل منصة وmerchant، مع callback/HMAC قابل للاختبار.
- أسماء وقنوات تسليم secrets ودورة rotation خارج API.
- SLA المتفق عليه تجاريًا؛ ثلاثة أيام عمل هو target baseline لإكمال الطلبات
التشغيلية من
requested_atووفقdue_atوتقويم الطلب، وليس ضمانًا للمزود. - مصفوفة العملات والضرائب إن تجاوزت المنصة العملة الحالية.
- عقد رسمي إن احتاجت المنصة balance أو settlement أو payout.
- سياسة الاحتفاظ بسجلات webhook وinvoice، ومكان الإقامة القانوني للبيانات.
- قائمة events الإضافية إن كانت المنصة تحتاج domain events غير الأربع الحالية.
لا تُغلق هذه النقاط بافتراضات من واجهة أو من package catalog؛ اطلب تحديثًا موثقًا للـ discovery/OpenAPI والعقد التجاري.
حدود بيانات المستندات الأولية: 128 خانة إجمالًا، و20 خانة اختيارية متكررة لكل نوع، و2000 حرف لكل قيمة مع تطبيق أي حد أدق خاص بالحقل. لا تتجاوز خانات المالك أو المفوض العدد المعلن.
18. إغلاق الربط وتسليم الدعم
تفصل خطة القبول بين مراجعة الوثائق، وبناء الموصل، ونشره، وتفعيل الحساب، والقبول المالي المشترك. لكل اختبار احفظ الإصدار والبيئة والحساب ومرجع العملية والنتيجة والجهة التي نفذته؛ لا تنسب اختبار BeanzPay المحلي للطرفين.
| اختبار القبول | الدليل المطلوب | المسؤولون |
|---|---|---|
| تسجيل شركة/عامل حر وإعادة الطلب وتجديد رابط المالك | نفس IDs ومرجع ثابت، نوع صحيح ومتطلبات متطابقة | المنصة وBeanzPay |
| صورة مالك وحقولها ومفوض بلا ملف | غياب صورة المالك يمنع الجاهزية؛ نصوص المفوض تكفي إذا لا يتطلب ملفًا | المنصة وBeanzPay |
| موظف/متجر/تاجر/بيئة خاطئة | رفض وصول دون تسريب أو تغيير سجل آخر | المنصة وBeanzPay |
| إصدار/تدوير/سحب اعتماد وموافقة المالك | أقل scopes، سر مرة واحدة، القديم مرفوض | المالك والمنصة وBeanzPay |
| دفع ناجح وفاشل ومتأخر وفقد استجابة | إثبات مالي ومراجع ثابتة ومنع التكرار | المنصة وBeanzPay والحساب المفعل |
| إشعار صحيح/محرف/مكرر/قديم وreplay | تحقق raw-body وربط event ودفتر dedupe | المنصة وBeanzPay |
| استرداد كامل وجزئي ومتزامن ومجهول | child دائم، إجمالي ومتبقٍ ومصدر تأكيد، لا POST بديل | المنصة وBeanzPay |
| Apple Pay وطرق الدفع وتعطيل المزود | طلب due_at، artifact صحيح، دليل effective واختبار الحساب | المنصة وعمليات BeanzPay |
| تغيير بوابة التاجر | قراءة واسترداد القديم على بوابته وحسابه وبيئته الأصلية | المنصة |
| اشتراك وفاتورة سداد عند إدراجهما بالنطاق | inquiry مملوك، بريد/إلغاء/دفع متأخر بلا ضريبة مزعومة | المنصة وBeanzPay |
قبل الاختبار يتفق الطرفان على نافذة التنفيذ وقناة التصعيد ومسؤولي التقنية والعمليات ومدد المتابعة. عند نتيجة مالية غير محسومة يوقف الموصل إنشاء بديل للعملية ويحفظها للتتبع؛ لا يمسحها ولا يعلن نجاحًا. يستأنف بعد قراءة أو مطابقة موثوقة، أو قرار عمليات موثق عند تعذر الحسم. إعادة الخدمة لا توسع الصلاحيات.
لمشكلة مستند أرسل: وقت المحاولة ومنطقته، X-Request-ID، application UUID،
نوع/slot المستند، HTTP، وأسماء الحقول المرفوضة وحجم الملف وMIME فقط. فرّق بين
القيم المخزنة وسجل تاريخي لم يُحفظ؛ لا تنسب سبب 422 قديمًا للتخمين. لا ترسل
محتوى المستند أو بيانات الهوية أو البنك أو المفاتيح. قراءة الطلب وتشخيصه
لا تتطلب إنشاء تسجيل جديد أو تغيير موافقات المالك أو تفعيلًا ماليًا.