العنوان الأساسي: https://api.lahsab.com. جميع المبالغ أعداد صحيحة بالدينار الجزائري (DZD، بدون سنتيمات). يعتمد الربط من خادم إلى خادم على الترويسة x-api-key للمصادقة، وتحدد البيئة من بادئة المفتاح (lsk_sandbox_ / lsk_live_). بعض نقاط النهاية عمومية (الصفحات المستضافة وإيداع وصل الدفع) ولا تتطلب أي مصادقة.

يغطي API واجهتين تكمل إحداهما الأخرى:

  • المدفوعات: كائن PaymentIntent لتحصيل مبلغ مرة واحدة. اللبنة الأساسية.
  • الفوترة: الزبائن والمنتجات والأسعار والاشتراكات والفواتير، عندما يتكرر التحصيل. راجع الاشتراكات.

خاصية Idempotency

تقبل جميع مسارات POST ترويسة Idempotency-Key. أعد إرسال المفتاح نفسه مع الجسم نفسه، فتحصل على الاستجابة نفسها دون إنشاء كائن ثان. هذه هي الحماية من النقر المزدوج وانتهاء المهلة وإعادة المحاولة.

-H "Idempotency-Key: sub-user_abc123-pro-mensuel"

اختر مفتاحا مشتقا من منطق عملك (sub-{userId}-{plan}، inv-{orderRef})، لا معرف UUID عشوائيا: معرف UUID يولد من جديد عند كل محاولة لا يحمي من شيء.

الحالةالاستجابة
المفتاح نفسه، الجسم نفسهالاستجابة الأصلية، تعاد كما هي.
المفتاح نفسه، جسم مختلف409 IDEMPOTENCY_KEY_REUSED.
المفتاح نفسه، والطلب الأصلي ما يزال قيد التنفيذ409 IDEMPOTENCY_KEY_IN_FLIGHT. أعد المحاولة.

إصدارات الكائنات

كل intent وفاتورة واشتراك وزبون ومنتج وسعر وقسيمة يحمل عددا صحيحا اسمه version يزيد عند كل كتابة، ويحمل كذلك updatedAt. الحقلان حاضران بالقيمة نفسها في استجابة API وفي حمولة webhook.

الحقل version هو ما يمكنك من تجاهل حمولة قديمة، سواء جاءت من إعادة تشغيل مفتاح الإدمبوتنس أو من تسليم webhook أعيدت محاولته. أما updatedAt فللعرض وتتبع الأخطاء، لا للترتيب. راجع ترتيب نسختك المحلية.

لغة صفحة الدفع

الصفحات المقدمة للمشتري (الدفع، الفاتورة المستضافة، بوابة الزبون) تعرض بالعربية أو بالفرنسية، مع الاتجاه من اليمين إلى اليسار. اللغة لا تستنتج من المتصفح: أنت من يقررها، لأنك تعرف مسبقا بأي لغة يقرأ مستخدمك تطبيقك.

اضبط أولا اللغات المفعلة ولغتك الافتراضية في لوحة التحكم، ضمن الإعدادات. تلقائيا تكون اللغتان مفعلتين والفرنسية هي الافتراضية.

بعدها تقبل المسارات POST /payments/intents و POST /invoices و POST /subscriptions و POST /customers حقلا اختياريا اسمه locale قيمته fr أو ar. تحدد اللغة الفعلية بهذا الترتيب:

  1. الحقل locale الممرر عند إنشاء الكائن،
  2. الحقل locale الخاص بالزبون المرتبط،
  3. لغتك الافتراضية،
  4. fr.
-d '{ "amount": 2500, "locale": "ar" }'

أي لغة لم تفعلها ترفض بالخطأ 400 MERCHANT_LOCALE_NOT_AVAILABLE.

اللغة تثبت عند الإنشاء، ثم ترجع في الحقل locale على الكائن وفي أحداث webhook. تغيير لغتك الافتراضية لاحقا لا يمس إذن فاتورة مفتوحة من قبل: الصفحة التي يراها المشتري لا تغير لغتها في منتصف الطريق. الكائنات اللاحقة هي التي تتبع الإعداد الجديد.

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

ما تقوم بهما يحدث للتجديدات
locale على الزبون (عند الإنشاء أو عبر PATCH /customers/:id)كل فاتورة صادرة تقرأه من جديد. أي تغيير يسري ابتداء من الفاتورة الموالية.
locale في POST /subscriptionsالاشتراك مثبت: كل فواتيره تحتفظ بهذه اللغة، مهما تغير الزبون.
لا هذا ولا ذاكتطبق لغتك الافتراضية.

الصفحات العمومية الثلاث ترجع كذلك الحقل locales، وهو قائمة لغاتك المفعلة. عليه يعتمد مبدل اللغة المعروض للمشتري، ويختفي عندما تفعل لغة واحدة فقط.

العودة إلى موقعك

الصفحات المعروضة على المشتري طريق مسدود: بمجرد إيداع الوصل، لا يجد أي منفذ للعودة إلى موقعك أو تطبيقك. الحقل returnUrl يضيف في هذه الصفحات زر «العودة إلى علامتك».

هناك ضابطان ينبغي معرفتهما قبل أي ربط.

الضابط الثاني: لا تحويل تلقائي أبدا. الزر ينتظر نقرة. الصفحة في تلك اللحظة ما زالت تحمل معلومات مفيدة (هل مر الوصل، متى ينتظر التأكيد، بمن يتصل)، فلا يخرج منها المشتري رغما عنه.

التصريح بوجهاتك

في لوحة التحكم، ضمن الإعدادات، إعدادان:

الإعدادالدور
عنوان العودة الافتراضييستعمل عندما لا يرسل نداء الواجهة البرمجية أي عنوان. اتركه فارغا حتى لا يظهر أي زر.
النطاقات المسموح بهاالقائمة البيضاء، 10 مدخلات على الأكثر. أي returnUrl ترسل عبر الواجهة البرمجية ولا يوجد نطاقها فيها ترفض بالخطأ 400 MERCHANT_RETURN_URL_NOT_ALLOWED.

نطاق عنوانك الافتراضي مسموح به تلقائيا، فلا داعي للتصريح به مرة أخرى.

المقبول: عناوين https، ومخططات التطبيقات (votreapp://، مع التصريح بها صراحة)، وhttp على loopback فقط (http://localhost:3000) من أجل دورة التطوير المحلية. وأي عنوان بشكل آخر يرفض عند حفظ إعداداتك بالخطأ 400 MERCHANT_RETURN_URL_INVALID. أما إذا أرسل في نداء إنشاء فيرفض بالخطأ 400 MERCHANT_RETURN_URL_NOT_ALLOWED نفسه الذي يرفض به أي نطاق خارج القائمة. ولا يتجاوز طول العنوان 2048 حرفا.

توجد هذه القائمة البيضاء لأن حساب lahsab يفتح دون تحقق مسبق. لولاها لحول أي شخص صفحة على lahsab.com إلى محول مفتوح نحو موقع تصيد. وهي الآلية نفسها المعتمدة في redirect URIs الخاصة بـ OAuth.

إرسال وجهة

تقبل النداءات POST /payments/intents وPOST /invoices وPOST /subscriptions وPOST /portal-sessions حقلا اختياريا اسمه returnUrl.

-d '{ "amount": 2500, "returnUrl": "https://app.votre-site.dz/commandes/42" }'

يحدد العنوان المعروض على المشتري بهذا الترتيب:

  1. حقل returnUrl الخاص بالكائن،
  2. عنوان الفاتورة التي انبثق عنها الـ intent، وإلا عنوان الاشتراك الذي انبثقت عنه الفاتورة،
  3. عنوان العودة الافتراضي،
  4. لا شيء، فلا يظهر أي زر.

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

ما تستقبله عند العودة

يكمل العنوان بمعاملات لحظة العرض، دون المساس بمعاملاتك:

المعاملالمحتوى
lahsab_intent_idالـ intent الذي دفعه المشتري للتو.
lahsab_invoice_idالفاتورة المرتبطة، إن وجدت.
lahsab_statusالحالة لحظة النقر (proof_submitted، confirmed، paid…).

بها تعرض «استلمنا وصلك، التأكيد في الطريق» بدل صفحة استقبال صامتة. هذه المعاملات غير موقعة وغير موثقة، فهي مجرد تسهيل في العرض: كل ما يلزم تطبيقك يمر عبر الـ webhook.

تطبيقات الهاتف

فضل الرابط الشامل (https://app.votre-site.dz/paiement/retour): يفتح تطبيقك إن كان مثبتا، ويرجع إلى موقعك إن لم يكن، ويمر عبر القائمة البيضاء مثل أي عنوان آخر. المخطط الخاص (votreapp://retour) يعمل أيضا، بشرط التصريح به ضمن نطاقاتك المسموح بها.

المدفوعات

إنشاء دفعة

POST /payments/intents

ينشئ نية تحصيل ويرجع checkoutUrl، وهو رابط الدفع المستضاف، الصالح لمدة 48 ساعة. وجه المشتري إليه.

الحقلالنوعمطلوبالتفاصيل
amountintنعمعدد صحيح موجب تماما (DZD).
descriptionstringلانص يظهر للمشتري على صفحة الدفع (280 حرفا كحد أقصى). بدونه لا يرى المشتري سوى مبلغ مجرد.
customerRefstringلامرجعك الخاص (مثلا معرف المستخدم). لا يظهر للمشتري أبدا.
methodstringلاccp أو baridimob: يحدد وسيلة الدفع مسبقا على الصفحة (ويمكن للمشتري تغييرها). 400 PAYMENT_METHOD_NOT_AVAILABLE إذا لم تكن هذه الوسيلة متاحة في إعداداتك.
localestringلاfr أو ar: لغة صفحة الدفع. انظر لغة صفحة الدفع. القيمة الافتراضية: لغتك الافتراضية.
returnUrlstringلاإلى أين يعاد المشتري انطلاقا من الصفحة. يجب أن يكون النطاق ضمن قائمتك البيضاء. انظر العودة إلى موقعك.
metadataobjectلاكائن JSON حر. لا يظهر للمشتري أبدا.

يجب أن تكون وسيلة تحصيل واحدة على الأقل مفعلة ومكتملة في إعداداتك (بريدي موب: صاحب الحساب مع RIP؛ CCP: صاحب الحساب مع الرقم والمفتاح والعنوان)، وإلا 409 MERCHANT_PAYMENT_DETAILS_MISSING. ينطبق هذا على البيئتين معا، بما فيها Sandbox: بدون هذه المعلومات لن يجد المشتري أين يحول المال، واختبار يتجاوز هذه الخطوة لا يختبر المسار الحقيقي.

الأخطاء: 400 VALIDATION_ERROR، 400 PAYMENT_METHOD_NOT_AVAILABLE، 400 MERCHANT_LOCALE_NOT_AVAILABLE، 400 MERCHANT_RETURN_URL_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 409 MERCHANT_PAYMENT_DETAILS_MISSING.

الطلب
curl -X POST https://api.lahsab.com/payments/intents \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{
    "amount": 2500,
    "customerRef": "user_abc123",
    "metadata": { "orderId": "CMD-1042" }
  }'

الاستجابة 201

{
  "id": "a7f3c2e1-9b4d-4e6a-8c1f-2d3e4f5a6b7c",
  "environment": "live",
  "customerRef": "user_abc123",
  "amount": 2500,
  "locale": "fr",
  "returnUrl": "https://votre-site.dz/merci",
  "status": "pending",
  "checkoutUrl": "https://dashboard.lahsab.com/pay/a7f3c2e1-9b4d-4e6a-8c1f-2d3e4f5a6b7c",
  "metadata": { "orderId": "CMD-1042" },
  "expiresAt": "2026-07-13T10:00:00.000Z",
  "proofs": [],
  "version": 1,
  "createdAt": "2026-07-11T10:00:00.000Z",
  "updatedAt": "2026-07-11T10:00:00.000Z"
}

الاطلاع على دفعة

GET /payments/intents/:id

يرجع الحالة الراهنة، بشرط أن تنتمي الدفعة إلى المفتاح وإلى بيئته. وإلا 404: الدفعة التابعة لبيئة أخرى غير مرئية.

الأخطاء: 401 AUTH_UNAUTHORIZED، 404 PAYMENT_INTENT_NOT_FOUND.

الطلب
curl https://api.lahsab.com/payments/intents/INTENT_ID \
  -H "x-api-key: lsk_live_CLE"

الاستجابة 200

{
  "id": "a7f3c2e1-9b4d-4e6a-8c1f-2d3e4f5a6b7c",
  "environment": "live",
  "amount": 2500,
  "method": "baridimob",
  "status": "proof_submitted",
  "checkoutUrl": "https://dashboard.lahsab.com/pay/a7f3c2e1-9b4d-4e6a-8c1f-2d3e4f5a6b7c",
  "expiresAt": "2026-07-13T10:00:00.000Z",
  "proofs": [
    {
      "id": "9c8b7a6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
      "originalName": "recu-baridimob.jpg",
      "mimeType": "image/jpeg",
      "size": 184230,
      "method": "baridimob",
      "submittedAt": "2026-07-11T10:12:00.000Z"
    }
  ],
  "version": 2,
  "createdAt": "2026-07-11T10:00:00.000Z",
  "updatedAt": "2026-07-11T10:12:00.000Z"
}

صفحة الدفع

GET /payments/checkout/:id

بدون أي مصادقة: هذا ما تقدمه الصفحة المستضافة لمتصفح المشتري. يرجع عرضا عموميا للدفعة: المبلغ والحالة ووسائل التحصيل الخاصة بالتاجر داخل methods، لكل وسيلة معلوماتها الخاصة (RIP لوسيلة بريدي موب، والرقم والمفتاح والعنوان للدفع عبر CCP). لا تظهر إلا الوسائل المفعلة والمكتملة. يعكس الحقل method الاختيار المسبق إن وجد، والمشتري يختار وسيلته على الصفحة. الحقل locale هو اللغة التي تعرض بها الصفحة، والحقل locales قائمة لغاتك المفعلة. لا مفتاح API ولا سر webhook ولا metadata أبدا.

الدفعة المنتهية الصلاحية (بعد 48 ساعة) ترجع بالحالة status: "expired".

ولا يميز هذا العرض بين دفعة اعتبر وصلها متوافقا ودفعة وصلها للتو: كلتاهما تخرجان بالحالة proof_submitted. وهذا مقصود. فلا ينبغي أن يعلم المشتري أن ملفا ما اجتاز التحليل، وإلا صار الإيداع منصة تجريب لصناعة وصل مزور ينجح. أما الحالة الحقيقية proof_validated فلا تدور إلا على واجهات التاجر: GET /payments/intents/:id وحدث webhook ولوحة التحكم.

الأخطاء: 404 PAYMENT_INTENT_NOT_FOUND.

الطلب
curl https://api.lahsab.com/payments/checkout/INTENT_ID

الاستجابة 200

{
  "id": "a7f3c2e1-9b4d-4e6a-8c1f-2d3e4f5a6b7c",
  "environment": "live",
  "amount": 2500,
  "method": "baridimob",
  "locale": "ar",
  "locales": ["fr", "ar"],
  "status": "pending",
  "expiresAt": "2026-07-13T10:00:00.000Z",
  "hasProof": false,
  "merchantConfigured": true,
  "methods": [
    {
      "method": "ccp",
      "holder": "SARL ACME",
      "ccpNumber": "0123456789",
      "ccpKey": "12",
      "address": "10 rue Didouche Mourad, Alger"
    },
    {
      "method": "baridimob",
      "holder": "SARL ACME",
      "rip": "00799999012345678912"
    }
  ],
  "merchant": {
    "displayName": "ACME Store",
    "paymentInstructions": "Le versement doit venir de votre propre compte.",
    "supportEmail": "support@acme.dz",
    "supportPhone": "0770000000"
  }
}

إيداع وصل الدفع

POST /payments/intents/:id/proof

بدون أي مصادقة: نقطة نهاية عمومية تستعملها صفحة الدفع (أو صفحتك الخاصة إن بنيت واحدة). الإرسال بصيغة multipart/form-data: حقل file مع حقل method (ccp أو baridimob)، أي الوسيلة التي استعملها المشتري فعلا. يمكن إغفال method عندما تكون وسيلة واحدة فقط متاحة أو عندما تحمل الدفعة اختيارا مسبقا صالحا. تنتقل الدفعة إلى proof_submitted وتحمل من الآن هذا method، وحدث webhook باسم payment.proof_submitted ينقله.

الملف محدود: صور (JPEG، PNG، WebP، HEIC) أو PDF، بحجم أقصاه 10 ميغابايت.

الأخطاء: 400 PAYMENT_PROOF_FILE_MISSING، 400 PAYMENT_METHOD_REQUIRED، 400 PAYMENT_METHOD_NOT_AVAILABLE، 404 PAYMENT_INTENT_NOT_FOUND، 409 PAYMENT_INVALID_TRANSITION، 409 PAYMENT_INTENT_EXPIRED، 413 FILE_TOO_LARGE، 415 FILE_TYPE_NOT_ALLOWED، 429 PAYMENT_PROOF_LIMIT_REACHED.

الطلب
curl -X POST https://api.lahsab.com/payments/intents/INTENT_ID/proof \
  -F "method=baridimob" \
  -F "file=@recu-baridimob.jpg"

الاستجابة 201: العرض العمومي للدفعة (بنفس شكل صفحة الدفع)، مع hasProof: true و status: "proof_submitted". لا بيانات حساسة.

المحاكاة (Sandbox)

نقاط نهاية خاصة ببيئة Sandbox لفرض كل سيناريو دون انتظار مشتر حقيقي. تمر عبر آلة الحالات الحقيقية ومسار التأكيد الحقيقي، وتصدر أحداث webhook الحقيقية: تطابق تام مع بيئة الإنتاج. إذا نوديت بمفتاح lsk_live_…: 400 ENVIRONMENT_NOT_ALLOWED. دليل الاستعمال الكامل في الاختبار في Sandbox.

محاكاة وصل الدفع

POST /payments/intents/:id/simulate/proof

يرفق وصلا اصطناعيا (ملف PDF بعنوان «PREUVE SIMULEE - SANDBOX»، يظهر في لوحة التحكم مثل أي وصل آخر) وينقل الدفعة إلى proof_submitted. يصدر الحدث payment.proof_submitted، ثم payment.proof_analyzed بتحليل محاكى حكمه match، ثم payment.proof_validated لأن هذا الحكم متوافق. أما الدفعة نفسها فتبقى في انتظار التأكيد: لا يؤكدها أي نظام آلي، تماما كما في الإنتاج.

نفس الضوابط المطبقة على الإيداع العمومي: دفعة منتهية الصلاحية ترد بالخطأ 409 PAYMENT_INTENT_EXPIRED، وحالة نهائية بالخطأ 409 PAYMENT_INVALID_TRANSITION.

الأخطاء: 400 ENVIRONMENT_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 404 PAYMENT_INTENT_NOT_FOUND، 409 PAYMENT_INVALID_TRANSITION، 409 PAYMENT_INTENT_EXPIRED.

الطلب
curl -X POST https://api.lahsab.com/payments/intents/INTENT_ID/simulate/proof \
  -H "x-api-key: lsk_sandbox_CLE"

الاستجابة 201: الدفعة بعد التحديث، وقيمة status هي proof_submitted، ثم proof_validated بمجرد صدور التحليل.

محاكاة التأكيد

POST /payments/intents/:id/simulate/confirm

تأكيد «وصول المال» عبر مسار الإشارة المعتاد: تنتقل الدفعة إلى confirmed، وحمولة حدث webhook باسم payment.confirmed تحمل confirmationSource: "manual" تماما كما في الإنتاج. إذا كانت الدفعة مرتبطة بفاتورة، تتم تسوية الفاتورة: يصدر invoice.paid وتتقدم فترة الاشتراك.

صالح انطلاقا من pending أو proof_submitted أو proof_validated. النداء آمن عند التكرار: إذا نودي مجددا على دفعة مؤكدة من قبل، يرجع الدفعة المؤكدة دون خطأ.

الأخطاء: 400 ENVIRONMENT_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 404 PAYMENT_INTENT_NOT_FOUND، 409 PAYMENT_INVALID_TRANSITION.

الطلب
curl -X POST https://api.lahsab.com/payments/intents/INTENT_ID/simulate/confirm \
  -H "x-api-key: lsk_sandbox_CLE"

الاستجابة 201: الدفعة بعد التحديث، وقيمة status هي confirmed.

محاكاة الرفض أو انتهاء الصلاحية

POST /payments/intents/:id/simulate/reject
POST /payments/intents/:id/simulate/expire

يرفض reject الدفعة: تنتقل إلى rejected ويصدر payment.rejected. سيناريو «وصل مودع لكن المال لم يصل» تحصل عليه بتسلسل simulate/proof ثم simulate/reject.

الحقلالنوعمطلوبالتفاصيل
reasonstringلاسبب الرفض.

يفرض expire انتهاء الصلاحية دون انتظار expiresAt. صالح فقط انطلاقا من pending، وإلا 409 PAYMENT_INVALID_TRANSITION. يصدر payment.expired.

الأخطاء: 400 ENVIRONMENT_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 404 PAYMENT_INTENT_NOT_FOUND، 409 PAYMENT_INVALID_TRANSITION.

الطلبات
# Rejeter : le motif est optionnel.
curl -X POST https://api.lahsab.com/payments/intents/INTENT_ID/simulate/reject \
  -H "x-api-key: lsk_sandbox_CLE" \
  -H "content-type: application/json" \
  -d '{ "reason": "Fonds jamais reçus" }'

# Expirer (uniquement depuis pending).
curl -X POST https://api.lahsab.com/payments/intents/INTENT_ID/simulate/expire \
  -H "x-api-key: lsk_sandbox_CLE"

الاستجابة 201: الدفعة بعد التحديث، وقيمة status هي rejected أو expired.

تشغيل ساعة الفوترة

POST /billing/simulate/tick

ينفذ ساعة الفوترة الآن، مقصورة على بياناتك في Sandbox (التجديدات، التأخيرات، انتهاء الصلاحية)، ويرجع تقريرها. بهذا يمكنك ملاحظة عدة دورات لاشتراك interval: "day" في دقائق معدودة.

قيمة purgedIdempotencyKeys تساوي دائما 0 في الوضع المقصور. الحد الأقصى 6 نداءات في الدقيقة.

الأخطاء: 400 ENVIRONMENT_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 429 RATE_LIMITED.

الطلب
curl -X POST https://api.lahsab.com/billing/simulate/tick \
  -H "x-api-key: lsk_sandbox_CLE"

الاستجابة: تقرير التنفيذ.

{
  "expiredIntents": 0,
  "issuedInvoices": 1,
  "markedOverdue": 0,
  "markedUncollectible": 0,
  "closedSubscriptions": 0,
  "purgedIdempotencyKeys": 0
}

البدء من جديد

DELETE /payments/sandbox-data

يمسح بيانات الاختبار الخاصة بك (الدفعات، الوصولات، الأحداث). في Sandbox فقط: مع مفتاح lsk_live_… يرجع 400 ENVIRONMENT_NOT_ALLOWED. مفيد بين جولتي اختبار للانطلاق من قاعدة نظيفة.

الأخطاء: 400 ENVIRONMENT_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED.

الطلب
curl -X DELETE https://api.lahsab.com/payments/sandbox-data \
  -H "x-api-key: lsk_sandbox_CLE"

الاستجابة 200: { "success": true }.

الزبائن

إنشاء زبون

POST /customers

الزبون هو الطرف الذي يدفع. وكل ما عداه (الاشتراكات، الفواتير) يرتبط به.

الحقلالنوعمطلوبالتفاصيل
externalIdstringلامعرف الزبون عندك. فريد لكل تاجر وبيئة.
namestringلاالاسم الظاهر على الفاتورة.
emailstringلا
phonestringلا
defaultMethodstringلاccp أو baridimob. يحدد مسبقا وسيلة الدفع على فواتير هذا الزبون، فقط إذا كانت متاحة في إعداداتك.
localestringلاfr أو ar: لغة الصفحات المرسلة إلى هذا الزبون، بما فيها تجديدات الاشتراك. انظر لغة صفحة الدفع.
metadataobjectلاكائن JSON حر.

املأ externalId: هو ما يعود إليك في كل حدث webhook، ويغنيك عن تخزين معرف id الذي يولده lahsab من جهتك.

الأخطاء: 400 VALIDATION_ERROR، 400 MERCHANT_LOCALE_NOT_AVAILABLE، 401 AUTH_UNAUTHORIZED، 409 CUSTOMER_ALREADY_EXISTS، 409 IDEMPOTENCY_KEY_REUSED.

الطلب
curl -X POST https://api.lahsab.com/customers \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: cust-user_abc123" \
  -d '{
    "externalId": "user_abc123",
    "name": "Yacine B.",
    "email": "yacine@example.dz",
    "phone": "0770000000",
    "defaultMethod": "baridimob"
  }'

الاستجابة 201

{
  "id": "c0ffee00-1111-4222-8333-444455556666",
  "environment": "live",
  "externalId": "user_abc123",
  "name": "Yacine B.",
  "email": "yacine@example.dz",
  "phone": "0770000000",
  "defaultMethod": "baridimob",
  "locale": "ar",
  "version": 1,
  "createdAt": "2026-07-13T10:00:00.000Z",
  "updatedAt": "2026-07-13T10:00:00.000Z"
}

قراءة زبون وتحديثه

GET /customers
GET /customers/:id
PATCH /customers/:id

GET /customers?externalId= هو نمط البحث ثم الإنشاء (lookup / upsert): ابحث بمعرفك الخاص، وأنشئ الزبون إذا رجعت القائمة فارغة. هكذا يحل الزبون دون تخزين أي معرف id من lahsab.

بدون مرشح، يعرض GET /customers زبائن البيئة الحالية.

يقبل PATCH الحقول name و email و phone و defaultMethod و locale و metadata. أما externalId فهو غير قابل للتعديل. مرر "locale": null للعودة إلى لغتك الافتراضية.

الأخطاء: 401 AUTH_UNAUTHORIZED، 404 CUSTOMER_NOT_FOUND.

الطلبات
# Lookup par votre propre identifiant : le motif upsert.
curl "https://api.lahsab.com/customers?externalId=user_abc123" \
  -H "x-api-key: lsk_live_CLE"

curl https://api.lahsab.com/customers/CUSTOMER_ID \
  -H "x-api-key: lsk_live_CLE"

curl -X PATCH https://api.lahsab.com/customers/CUSTOMER_ID \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{ "phone": "0771111111" }'

الاستجابة 200: مصفوفة للقائمة، وكائن الزبون للنداءين الآخرين.

المنتجات والأسعار

المنتجات

POST /products
GET /products
PATCH /products/:id

المنتج هو ما تبيعه، لا تسعيرته. التسعيرة هي السعر (أدناه)، والمنتج الواحد يمكن أن يحمل عدة أسعار.

الحقلالنوعمطلوبالتفاصيل
namestringنعممثلا «Abonnement Pro».
descriptionstringلا
metadataobjectلاكائن JSON حر.

يقبل PATCH الحقول name و description و active و metadata. مرر active: false لسحب المنتج من الكتالوج دون المساس بالسجل التاريخي.

الأخطاء: 400 VALIDATION_ERROR، 401 AUTH_UNAUTHORIZED، 404 PRODUCT_NOT_FOUND.

الطلبات
curl -X POST https://api.lahsab.com/products \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: prod-pro" \
  -d '{ "name": "Abonnement Pro", "description": "Accès complet" }'

curl https://api.lahsab.com/products \
  -H "x-api-key: lsk_live_CLE"

curl -X PATCH https://api.lahsab.com/products/PRODUCT_ID \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{ "name": "Abonnement Pro (mensuel)" }'

الاستجابة 201

{
  "id": "3b2a1c09-8d7e-4f65-a432-1098765fedcb",
  "environment": "live",
  "name": "Abonnement Pro",
  "description": "Accès complet",
  "active": true,
  "version": 1,
  "createdAt": "2026-07-13T10:00:00.000Z",
  "updatedAt": "2026-07-13T10:00:00.000Z"
}

إنشاء سعر

POST /prices

السعر يربط مبلغا ووتيرة بمنتج. وهو ما يشترك فيه الزبون.

الحقلالنوعمطلوبالتفاصيل
productIduuidنعمالمنتج المسعر.
amountintنعمعدد صحيح موجب تماما (DZD).
typestringنعمone_time أو recurring.
intervalstringإذا كان recurringday، week، month، year.
intervalCountintلامضاعف الفترة (من 1 إلى 52). القيمة الافتراضية 1.
trialPeriodDaysintلاتجربة مجانية (من 0 إلى 365).
nicknamestringلاتسمية داخلية.
metadataobjectلاكائن JSON حر.

الاشتراك الفصلي يعبر عنه بالمزج بين interval: "month" و intervalCount: 3.

الأخطاء: 400 VALIDATION_ERROR، 401 AUTH_UNAUTHORIZED، 404 PRODUCT_NOT_FOUND، 409 IDEMPOTENCY_KEY_REUSED.

الطلب
curl -X POST https://api.lahsab.com/prices \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: price-pro-mensuel-2500" \
  -d '{
    "productId": "3b2a1c09-8d7e-4f65-a432-1098765fedcb",
    "amount": 2500,
    "type": "recurring",
    "interval": "month",
    "intervalCount": 1,
    "nickname": "Pro mensuel"
  }'

الاستجابة 201

{
  "id": "9f8e7d6c-5b4a-4321-9876-543210fedcba",
  "environment": "live",
  "productId": "3b2a1c09-8d7e-4f65-a432-1098765fedcb",
  "productName": "Abonnement Pro",
  "nickname": "Pro mensuel",
  "amount": 2500,
  "type": "recurring",
  "interval": "month",
  "intervalCount": 1,
  "active": true,
  "version": 1,
  "createdAt": "2026-07-13T10:00:00.000Z",
  "updatedAt": "2026-07-13T10:00:00.000Z"
}

عرض الأسعار وأرشفتها

GET /prices
PATCH /prices/:id

يعرض GET /prices?productId=&active=true الكتالوج القابل للبيع لمنتج معين.

لذلك لا يقبل PATCH سوى nickname و active و metadata. أرشفة سعر لا تمس الاشتراكات القائمة عليه: تستمر فوترتها بالمبلغ المتفق عليه.

الأخطاء: 400 VALIDATION_ERROR، 401 AUTH_UNAUTHORIZED، 404 PRICE_NOT_FOUND.

الطلبات
curl "https://api.lahsab.com/prices?productId=PRODUCT_ID&active=true" \
  -H "x-api-key: lsk_live_CLE"

# Archiver un tarif : on ne le modifie pas, on le retire du catalogue.
curl -X PATCH https://api.lahsab.com/prices/PRICE_ID \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{ "active": false }'

الاستجابة 200: السعر بعد التحديث، وقيمة active هي false.

الكوبونات

POST /coupons
GET /coupons
PATCH /coupons/:id

الكوبون يعرف تخفيضا قابلا لإعادة الاستعمال، بنسبة مئوية أو بمبلغ ثابت. يطبق عند إنشاء الاشتراك (الحقل couponId في POST /subscriptions) ويعدل مبلغ كل فاتورة حسب قيمة duration.

الحقلالنوعمطلوبالتفاصيل
namestringنعميعرض للمشتري على الفاتورة وصفحة الدفع.
percentOffintأحد الحقلينمن 1 إلى 100. لا يجتمع مع amountOff.
amountOffintأحد الحقلينعدد صحيح موجب (د.ج). لا يجتمع مع percentOff.
durationstringنعمforever (كل الفواتير) أو once (الفاتورة الأولى فقط).
metadataobjectلاكائن JSON حر.

أرشفة كوبون (active: false) تمنع تطبيقه على الاشتراكات الجديدة. أما الاشتراكات التي تحمله فتحتفظ بتخفيضها: الالتزام الممنوح يبقى ساريا.

الكوبون بالنسبة المئوية يواكب تغيرات الأسعار: -50 % تبقى -50 % مهما كانت التسعيرة المعتمدة لاحقا.

كل فاتورة مخفضة تحمل لقطة تدقيق: amountSubtotal (المبلغ الكامل) و discountAmount (التخفيض المطبق) و couponId و couponName. تبقى الفاتورة مفهومة بذاتها حتى لو أرشف الكوبون أو أعيدت تسميته لاحقا.

الأخطاء: 400 VALIDATION_ERROR، 401 AUTH_UNAUTHORIZED، 404 COUPON_NOT_FOUND، 409 IDEMPOTENCY_KEY_REUSED.

الطلبات
curl -X POST https://api.lahsab.com/coupons \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: coupon-founder" \
  -d '{
    "name": "founder",
    "percentOff": 50,
    "duration": "forever"
  }'

curl "https://api.lahsab.com/coupons?active=true" \
  -H "x-api-key: lsk_live_CLE"

# Archiver un coupon : il ne s'applique plus aux nouveaux abonnements.
curl -X PATCH https://api.lahsab.com/coupons/COUPON_ID \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{ "active": false }'

الاستجابة 201

{
  "id": "7d6c5b4a-3210-4fed-9cba-876543210fed",
  "environment": "live",
  "name": "founder",
  "percentOff": 50,
  "duration": "forever",
  "active": true,
  "version": 1,
  "createdAt": "2026-07-23T10:00:00.000Z",
  "updatedAt": "2026-07-23T10:00:00.000Z"
}

الاشتراكات

إنشاء اشتراك

POST /subscriptions

نقطة الدخول إلى مسار الشراء. نداء واحد: ينشأ الاشتراك، وتصدر الفاتورة الأولى، وتحمل الاستجابة رابط الدفع. وهو المقابل لما يقدمه Stripe Checkout.

الحقلالنوعمطلوبالتفاصيل
customerIduuidنعمالزبون صاحب الاشتراك.
priceIduuidنعميجب أن يكون سعرا recurring و active.
daysUntilDueintلامهلة الدفع الممنوحة (من 0 إلى 365). القيمة الافتراضية 7.
invoiceLeadDaysintلاعدد الأيام قبل نهاية الفترة التي تصدر فيها فاتورة التجديد. القيمة الافتراضية 7.
trialPeriodDaysintلاتجربة مجانية. لها الأولوية على تجربة السعر.
cancelAtdatetimeلاأجل محدد: ينتهي الاشتراك في هذا التاريخ مهما كانت فترة السعر. يجب أن يكون في المستقبل (وبعد نهاية التجربة إن وجدت).
couponIduuidلايطبق كوبونا نشطا: تخفض كل فاتورة حسب قيمة duration. كوبون واحد لكل اشتراك.
localestringلاfr أو ar: يثبت لغة كل فواتير هذا الاشتراك، بما فيها التجديدات. بدونه تقرأ كل فاتورة لغة الزبون من جديد.
returnUrlstringلاإلى أين يعاد المشتري انطلاقا من الصفحة. يجب أن يكون النطاق ضمن قائمتك البيضاء. انظر العودة إلى موقعك.
metadataobjectلاكائن JSON حر.

يولد الاشتراك بالحالة incomplete: وجه المشتري إلى latestInvoice.hostedInvoiceUrl. وسينتقل إلى active عند استقبال invoice.paid، وليس قبل ذلك أبدا.

مع تجربة مجانية: لا فاتورة فورية. يولد الاشتراك بالحالة trialing، وتساوي currentPeriodEnd قيمة trialEnd، ويكون latestInvoice غائبا.

مع cancelAt: تتقدم الفترات كالمعتاد، لكن كل فترة جديدة تتوقف عند cancelAt على أقصى تقدير. الفترة الأخيرة تكون إذن مبتورة، وتعرض فاتورتها الفترة الحقيقية، ولا يصدر أي تجديد بعدها. المثال النموذجي: اشتراك سنوي يباع في سبتمبر وينتهي في 30 جوان، بفاتورة واحدة.

الأخطاء: 400 VALIDATION_ERROR، 400 SUBSCRIPTION_CANCEL_AT_INVALID، 400 MERCHANT_LOCALE_NOT_AVAILABLE، 400 MERCHANT_RETURN_URL_NOT_ALLOWED، 400 COUPON_ARCHIVED، 401 AUTH_UNAUTHORIZED، 404 CUSTOMER_NOT_FOUND، 404 PRICE_NOT_FOUND، 404 COUPON_NOT_FOUND، 400 PRICE_INACTIVE، 400 PRICE_NOT_RECURRING، 409 MERCHANT_PAYMENT_DETAILS_MISSING، 409 IDEMPOTENCY_KEY_REUSED.

الطلب
curl -X POST https://api.lahsab.com/subscriptions \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: sub-user_abc123-pro-mensuel" \
  -d '{
    "customerId": "c0ffee00-1111-4222-8333-444455556666",
    "priceId": "9f8e7d6c-5b4a-4321-9876-543210fedcba",
    "daysUntilDue": 7,
    "invoiceLeadDays": 7
  }'

# Terme fixe + réduction : un pass saison à -50 %, qui se termine le 30 juin.
curl -X POST https://api.lahsab.com/subscriptions \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: sub-user_abc123-pass-saison" \
  -d '{
    "customerId": "c0ffee00-1111-4222-8333-444455556666",
    "priceId": "PASS_SAISON_PRICE_ID",
    "cancelAt": "2027-06-30T00:00:00.000Z",
    "couponId": "COUPON_ID"
  }'

الاستجابة 201

{
  "id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
  "environment": "live",
  "status": "incomplete",
  "collectionMethod": "send_invoice",
  "daysUntilDue": 7,
  "invoiceLeadDays": 7,
  "currentPeriodStart": "2026-07-13T00:00:00.000Z",
  "currentPeriodEnd": "2026-08-13T00:00:00.000Z",
  "cancelAtPeriodEnd": false,
  "latestInvoice": {
    "id": "f1e2d3c4-b5a6-4978-8b1c-2d3e4f5a6b7c",
    "number": "LSB-2026-0042",
    "status": "open",
    "amountDue": 2500,
    "dueDate": "2026-07-20T00:00:00.000Z",
    "hostedInvoiceUrl": "https://dashboard.lahsab.com/invoice/f1e2d3c4-b5a6-4978-8b1c-2d3e4f5a6b7c",
    "version": 2,
    "createdAt": "2026-07-13T10:00:00.000Z",
    "updatedAt": "2026-07-13T10:00:00.000Z"
  },
  "version": 2,
  "createdAt": "2026-07-13T10:00:00.000Z",
  "updatedAt": "2026-07-13T10:00:00.000Z"
}

الاطلاع على اشتراك

GET /subscriptions
GET /subscriptions/:id

المرشحات: customerId، status.

currentPeriodEnd هو التاريخ الوحيد المهم: معناه «مدفوع حتى هذا التاريخ». ولا يتقدم إلا عند دفع فاتورة الفترة الموالية، ولا يتقدم أبدا لمجرد صدور فاتورة. راجع القاعدة الذهبية.

يعرض الاشتراك أيضا cancelAt (الأجل المحدد إن وجد) و discount.coupon (الكوبون المطبق)، في API كما في حمولات webhooks.

الأخطاء: 401 AUTH_UNAUTHORIZED، 404 SUBSCRIPTION_NOT_FOUND.

الطلبات
curl "https://api.lahsab.com/subscriptions?customerId=CUSTOMER_ID&status=active" \
  -H "x-api-key: lsk_live_CLE"

curl https://api.lahsab.com/subscriptions/SUBSCRIPTION_ID \
  -H "x-api-key: lsk_live_CLE"

الاستجابة 200: الاشتراك متضمنا customer و price و latestInvoice.

الإلغاء والاستئناف

POST /subscriptions/:id/cancel
POST /subscriptions/:id/resume
الحقلالنوعمطلوبالتفاصيل
atPeriodEndboolلاالقيمة الافتراضية true.

atPeriodEnd: true (الوضع الافتراضي) يبرمج الإلغاء: تصبح قيمة cancelAtPeriodEnd هي true، ويبقى الاشتراك active حتى نهاية الفترة المدفوعة، ولن تصدر أي فاتورة تجديد. وهذا هو السلوك المطلوب: الزبون دفع حتى currentPeriodEnd ومن حقه الاستفادة إلى ذلك التاريخ.

atPeriodEnd: false ينهي الاشتراك فورا: الحالة canceled مع ضبط endedAt. والفترة المدفوعة تضيع.

يتراجع resume عن إلغاء مبرمج ما دامت الفترة جارية. أما اشتراك صار canceled فلا يستأنف: 409 SUBSCRIPTION_INVALID_TRANSITION.

الأخطاء: 401 AUTH_UNAUTHORIZED، 404 SUBSCRIPTION_NOT_FOUND، 409 SUBSCRIPTION_ALREADY_CANCELED، 409 SUBSCRIPTION_INVALID_TRANSITION.

الطلبات
# Par défaut : à la fin de la période déjà payée.
curl -X POST https://api.lahsab.com/subscriptions/SUBSCRIPTION_ID/cancel \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{ "atPeriodEnd": true }'

# Revenir sur une annulation programmée.
curl -X POST https://api.lahsab.com/subscriptions/SUBSCRIPTION_ID/resume \
  -H "x-api-key: lsk_live_CLE"

الاستجابة 201

{
  "id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
  "status": "active",
  "cancelAtPeriodEnd": true,
  "canceledAt": "2026-07-13T10:00:00.000Z",
  "currentPeriodEnd": "2026-08-13T00:00:00.000Z"
}

الفواتير

إنشاء فاتورة لمرة واحدة

POST /invoices

فاتورة خارج أي اشتراك: خدمة، تسبيق، طلبية. تصدر نهائية (أي مرقمة) فور إنشائها، و hostedInvoiceUrl هو رابط الصفحة التي ترسلها إلى الزبون.

الحقلالنوعمطلوبالتفاصيل
customerIduuidنعم
amountintنعمعدد صحيح موجب تماما (DZD).
descriptionstringلاموضوع الفوترة (280 حرفا كحد أقصى).
dueDatedateلاتاريخ الاستحقاق (YYYY-MM-DD). القيمة الافتراضية: يومان.
localestringلاfr أو ar: لغة الفاتورة المستضافة. القيمة الافتراضية: لغة الزبون، وإلا لغتك.
returnUrlstringلاإلى أين يعاد المشتري انطلاقا من الصفحة. يجب أن يكون النطاق ضمن قائمتك البيضاء. انظر العودة إلى موقعك.

أما فواتير الاشتراكات فتصدر تلقائيا، ولا حاجة لإنشائها بنفسك.

الأخطاء: 400 VALIDATION_ERROR، 400 MERCHANT_LOCALE_NOT_AVAILABLE، 400 MERCHANT_RETURN_URL_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 404 CUSTOMER_NOT_FOUND، 409 MERCHANT_PAYMENT_DETAILS_MISSING، 409 IDEMPOTENCY_KEY_REUSED.

الطلب
curl -X POST https://api.lahsab.com/invoices \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: inv-CMD-1042" \
  -d '{
    "customerId": "c0ffee00-1111-4222-8333-444455556666",
    "amount": 12000,
    "description": "Installation sur site",
    "dueDate": "2026-07-31"
  }'

الاستجابة 201

{
  "id": "a1b2c3d4-e5f6-4789-8abc-def012345678",
  "environment": "live",
  "customerId": "c0ffee00-1111-4222-8333-444455556666",
  "number": "LSB-2026-0043",
  "status": "open",
  "amountDue": 12000,
  "amountPaid": 0,
  "description": "Installation sur site",
  "locale": "fr",
  "dueDate": "2026-07-31T00:00:00.000Z",
  "finalizedAt": "2026-07-13T10:00:00.000Z",
  "hostedInvoiceUrl": "https://dashboard.lahsab.com/invoice/a1b2c3d4-e5f6-4789-8abc-def012345678",
  "version": 2,
  "createdAt": "2026-07-13T10:00:00.000Z",
  "updatedAt": "2026-07-13T10:00:00.000Z"
}

الاطلاع على فاتورة

GET /invoices
GET /invoices/:id

المرشحات: customerId، subscriptionId، status.

GET /invoices?status=open هو قائمة المستحقات غير المدفوعة عندك. و status=uncollectible هو قائمة الخسائر.

فاتورة الاشتراك تحمل periodStart / periodEnd، وتحمل checkoutUrl ما دامت لها دفعة قابلة للدفع جارية.

الأخطاء: 401 AUTH_UNAUTHORIZED، 404 INVOICE_NOT_FOUND.

الطلبات
curl "https://api.lahsab.com/invoices?customerId=CUSTOMER_ID&status=open" \
  -H "x-api-key: lsk_live_CLE"

curl https://api.lahsab.com/invoices/INVOICE_ID \
  -H "x-api-key: lsk_live_CLE"

الاستجابة 200: الفاتورة، أو مصفوفة في حالة القائمة.

الإلغاء

POST /invoices/:id/void

يلغي void فاتورة بالحالة open: لم تعد قابلة للدفع، لكنها تحتفظ برقمها (التسلسل القانوني لا يعاد استعماله). والفاتورة المدفوعة لا تلغى (409 INVOICE_ALREADY_PAID).

الأخطاء: 401 AUTH_UNAUTHORIZED، 404 INVOICE_NOT_FOUND، 409 INVOICE_ALREADY_PAID، 409 INVOICE_INVALID_TRANSITION.

الطلب
# Annuler une facture ouverte.
curl -X POST https://api.lahsab.com/invoices/INVOICE_ID/void \
  -H "x-api-key: lsk_live_CLE"

الاستجابة 201: الفاتورة بعد التحديث (قيمة status هي void).

صفحة الفاتورة

GET /invoices/:id/public

بدون أي مصادقة: هذا ما تقدمه صفحة الفاتورة المستضافة (/invoice/:id) لمتصفح زبونك. وهو الرابط الذي تلصقه في محادثة WhatsApp.

يرجع عرضا عموميا: المبلغ وتاريخ الاستحقاق والحالة ووسائل التحصيل الخاصة بك داخل methods (بنفس شكل صفحة الدفع، الوسائل المفعلة والمكتملة فقط). لا مفتاح ولا سر ولا metadata ولا externalId أبدا.

الأخطاء: 404 INVOICE_NOT_FOUND.

الطلب
curl https://api.lahsab.com/invoices/INVOICE_ID/public

الاستجابة 200

{
  "id": "f1e2d3c4-b5a6-4978-8b1c-2d3e4f5a6b7c",
  "number": "LSB-2026-0042",
  "status": "open",
  "environment": "live",
  "amountDue": 2500,
  "amountPaid": 0,
  "locale": "ar",
  "locales": ["fr", "ar"],
  "dueDate": "2026-07-20T00:00:00.000Z",
  "customerName": "Yacine B.",
  "hasProof": false,
  "merchantConfigured": true,
  "methods": [
    {
      "method": "baridimob",
      "holder": "SARL ACME",
      "rip": "00799999012345678912"
    }
  ],
  "merchant": {
    "displayName": "ACME Store",
    "supportEmail": "support@acme.dz"
  }
}

بوابة الزبون

فتح جلسة بوابة

POST /portal-sessions

يرجع عنوان URL يجد فيه زبونك اشتراكه وفواتيره وزرا للدفع. إنها صفحة «تسيير اشتراكي» التي لا تحتاج إلى بنائها بنفسك.

الحقلالنوعمطلوبالتفاصيل
customerIduuidنعم
returnUrlstringلاإلى أين يعاد المشتري انطلاقا من الصفحة. يجب أن يكون النطاق ضمن قائمتك البيضاء. انظر العودة إلى موقعك.

الرابط صالح لمدة ساعة واحدة. والصفحة نفسها (/portal/:token) عمومية، يفتحها حامل الرابط.

الأخطاء: 400 VALIDATION_ERROR، 400 MERCHANT_RETURN_URL_NOT_ALLOWED، 401 AUTH_UNAUTHORIZED، 404 CUSTOMER_NOT_FOUND.

الطلب
curl -X POST https://api.lahsab.com/portal-sessions \
  -H "x-api-key: lsk_live_CLE" \
  -H "content-type: application/json" \
  -d '{ "customerId": "c0ffee00-1111-4222-8333-444455556666" }'

الاستجابة 201

{
  "url": "https://dashboard.lahsab.com/portal/ps_7f3c2e1a9b4d4e6a8c1f2d3e",
  "expiresAt": "2026-07-13T11:00:00.000Z"
}

بنية الخطأ

كل الأخطاء تشترك في الشكل نفسه. اختبر قيمة errorCode (فهي ثابتة)، لا قيمة message.

{
  "statusCode": 404,
  "errorCode": "PAYMENT_INTENT_NOT_FOUND",
  "message": "Payment intent not found",
  "timestamp": "2026-07-11T10:00:00.000Z",
  "path": "/payments/intents/a7f3c2e1-9b4d-4e6a-8c1f-2d3e4f5a6b7c"
}
errorCodeالحالةالمعنى
VALIDATION_ERROR400جسم طلب غير صالح، بما في ذلك PATCH على حقل غير قابل للتعديل في سعر.
PAYMENT_PROOF_FILE_MISSING400لا ملف وصل مرفق.
PAYMENT_METHOD_REQUIRED400عدة وسائل متاحة: يجب أن يحدد الوصل method.
PAYMENT_METHOD_NOT_AVAILABLE400وسيلة دفع معطلة أو غير مهيأة عند التاجر.
MERCHANT_LOCALE_NOT_AVAILABLE400اللغة المطلوبة غير مفعلة في إعداداتك.
MERCHANT_RETURN_URL_NOT_ALLOWED400عنوان returnUrl خارج قائمة نطاقاتك المسموح بها، أو بشكل غير مقبول.
MERCHANT_RETURN_URL_INVALID400عنوان عودة بشكل غير مقبول، عند حفظ إعداداتك.
ENVIRONMENT_NOT_ALLOWED400نقطة نهاية خاصة ببيئة Sandbox نوديت بمفتاح الإنتاج.
PRICE_INACTIVE400هذا السعر مؤرشف، لم يعد الاشتراك فيه ممكنا.
PRICE_NOT_RECURRING400الاشتراك يتطلب سعرا recurring.
AUTH_UNAUTHORIZED401مفتاح API مفقود أو غير صالح.
PAYMENT_INTENT_NOT_FOUND404دفعة مجهولة، أو تابعة لتاجر آخر أو بيئة أخرى.
CUSTOMER_NOT_FOUND404زبون مجهول.
PRODUCT_NOT_FOUND404منتج مجهول.
PRICE_NOT_FOUND404سعر مجهول.
SUBSCRIPTION_NOT_FOUND404اشتراك مجهول.
INVOICE_NOT_FOUND404فاتورة مجهولة.
PAYMENT_INVALID_TRANSITION409انتقال حالة غير ممكن.
PAYMENT_INTENT_EXPIRED409رابط دفع منتهي الصلاحية (بعد 48 ساعة).
MERCHANT_PAYMENT_DETAILS_MISSING409لا وسيلة تحصيل مفعلة ومكتملة.
CUSTOMER_ALREADY_EXISTS409قيمة externalId هذه مستعملة من قبل في هذه البيئة.
SUBSCRIPTION_ALREADY_CANCELED409اشتراك منته من قبل.
SUBSCRIPTION_INVALID_TRANSITION409انتقال حالة غير ممكن.
INVOICE_ALREADY_PAID409الفاتورة المدفوعة لا تلغى.
INVOICE_INVALID_TRANSITION409انتقال حالة غير ممكن.
IDEMPOTENCY_KEY_REUSED409نفس Idempotency-Key مع جسم مختلف.
IDEMPOTENCY_KEY_IN_FLIGHT409الطلب الأصلي ما يزال قيد التنفيذ. أعد المحاولة.
FILE_TOO_LARGE413ملف وصل أكبر من الحد المسموح (10 ميغابايت كحد أقصى).
FILE_TYPE_NOT_ALLOWED415ملف وصل من نوع غير مقبول (المقبول: صور أو PDF).
PAYMENT_PROOF_LIMIT_REACHED429عدد كبير جدا من الوصولات المودعة على هذه الدفعة.
RATE_LIMITED429عدد كبير جدا من الطلبات.