التوثيق
الدمججلسة الدفع

بعد إتمام الدفع

رجّع العميل لموقعك، أو سيب XPay يعرض صفحة شكر مستضافة. بالإضافة لإزاي تتعامل مع عمليات الدفع الفاشلة وليه الـ webhook هو مصدر الحقيقة.

afterCompletion بيقرّر اللي العميل بيشوفه في اللحظة اللي الدفع بينجح فيها. شكلين: redirect لرابط على موقعك، أو صفحة تأكيد مستضافة بيعرضها XPay. اختار واحد لكل جلسة؛ وتقدر تغيّره في PATCH طول ما الجلسة لسه مفتوحة.

فيه حقل منفصل، cancelUrl، بيقرّر العميل يروح فين لو فشلت عملية دفع على الصفحة المستضافة (بطاقة مرفوضة، أو 3D Secure مرفوض، أو انتهاء مهلة وسيلة محلية). اختياري وصالح بس لـ uiMode: "hosted".

أي خيار تختاره، صفحة الـ redirect أو التأكيد مش هي المكان اللي بتأكّد فيه الدفع. السيرفر بتاعك لازم يستنى webhook الـ checkout.session.completed علشان يعلّم الطلب مدفوع فعلًا. العملاء ممكن يقفلوا التبويب، أو يضربوا الرابط بالغلط، أو ما يوصلوش للـ redirect خالص.

الخيارين

afterCompletion.typeاللي العميل بيشوفه عند النجاحإمتى تختاره
redirectXPay بيبعته لـ redirect.url بتاعك. وإنت بتعرض صفحة الشكر بنفسك.عندك صفحة عودة بتناسب علامتك التجارية وسياق الطلب. الموصى به لأغلب المواقع.
hosted_confirmationXPay بيعرض صفحة نجاح جاهزة برسالة مخصّصة اختيارية وزر عودة.معندكش صفحة عودة (لسه)، أو لعمليات دفع لمرة واحدة بناء صفحة ليها مبالغة.

afterCompletion نفسه هو الحقل الوحيد المطلوب في POST /checkout/sessions. عدم إرسال أي من الحقلين أصلًا بيبقى خطأ تحقّق.

redirect لرابطك

ظبّط type: "redirect" وقدّم رابط HTTPS على نطاق إنت متحكّم فيه. XPay بيبعت العميل للرابط ده على طول بعد ما الدفع يتم.

curl -X POST https://api.xpay.app/checkout/sessions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "afterCompletion": {
      "type": "redirect",
      "redirect": { "url": "https://yourshop.example/order/{CHECKOUT_SESSION_ID}" }
    },
    "lineItems": [ { "price": "price_test_xyz", "quantity": 1 } ]
  }'
await fetch("https://api.xpay.app/checkout/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.XPAY_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    afterCompletion: {
      type: "redirect",
      redirect: { url: "https://yourshop.example/order/{CHECKOUT_SESSION_ID}" },
    },
    lineItems: [{ price: "price_test_xyz", quantity: 1 }],
  }),
});
import os, requests

requests.post(
    "https://api.xpay.app/checkout/sessions",
    headers={
        "Authorization": f"Bearer {os.environ['XPAY_SECRET_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "afterCompletion": {
            "type": "redirect",
            "redirect": {"url": "https://yourshop.example/order/{CHECKOUT_SESSION_ID}"},
        },
        "lineItems": [{"price": "price_test_xyz", "quantity": 1}],
    },
    timeout=10,
)

كام قاعدة:

  • redirect.url مطلوب لما type: "redirect". إرسال redirect مع type: "hosted_confirmation" بيتم رفضه.
  • HTTPS، على نطاق إنت متحكّم فيه. localhost ماشي في وضع الاختبار بس مرفوض في الحساب الفعلي.
  • مفيش query parameters بيضيفها XPay. العميل بيوصل لرابطك باللي إنت حطّيته فيه. لو محتاج تعرف الجلسة دي أنهي واحدة، استخدم رمز القالب اللي تحت.

قالب {CHECKOUT_SESSION_ID}

حُط الرمز الحرفي {CHECKOUT_SESSION_ID} في أي مكان في redirect.url وXPay بيستبدله بمعرّف الجلسة قبل ما يحفظ الرابط. مفيد لما ما تكونش عايز تمرّر المعرّف عبر حالتك إنت.

{
  "redirect": {
    "url": "https://yourshop.example/order/{CHECKOUT_SESSION_ID}",
  },
}

لجلسة بـ id: "cs_test_AbC123..."، العميل بيتحوّل لـ https://yourshop.example/order/cs_test_AbC123.... اقرا الـ cs_* من المسار على صفحة العودة بتاعتك لو عايز تعرض تفاصيل الطلب (استدعِ GET /checkout/sessions/:id علشان تجيب آخر حالة).

اعرض صفحة تأكيد مستضافة

ظبّط type: "hosted_confirmation" لو معندكش صفحة عودة. XPay بيعرض للعميل صفحة شكر باسم نشاطك التجاري وعلامة صح، وده آخر المسار.

curl -X POST https://api.xpay.app/checkout/sessions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "afterCompletion": {
      "type": "hosted_confirmation",
      "hostedConfirmation": {
        "customMessage": "Thanks! Your order will ship within 2 business days.",
        "returnUrl": "https://yourshop.example"
      }
    },
    "lineItems": [ { "price": "price_test_xyz", "quantity": 1 } ]
  }'
await fetch("https://api.xpay.app/checkout/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.XPAY_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    afterCompletion: {
      type: "hosted_confirmation",
      hostedConfirmation: {
        customMessage: "Thanks! Your order will ship within 2 business days.",
        returnUrl: "https://yourshop.example",
      },
    },
    lineItems: [{ price: "price_test_xyz", quantity: 1 }],
  }),
});
import os, requests

requests.post(
    "https://api.xpay.app/checkout/sessions",
    headers={
        "Authorization": f"Bearer {os.environ['XPAY_SECRET_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "afterCompletion": {
            "type": "hosted_confirmation",
            "hostedConfirmation": {
                "customMessage": "Thanks! Your order will ship within 2 business days.",
                "returnUrl": "https://yourshop.example",
            },
        },
        "lineItems": [{"price": "price_test_xyz", "quantity": 1}],
    },
    timeout=10,
)

الحقلين المتداخلين الاتنين اختياريين.

الحقلالافتراضيملاحظات
customMessageسطر على شكل عبارة بينتهي باسم نشاطك التجاري (الاسم المعروض من إعدادات العلامة التجارية في لوحة التحكم).لحد 500 حرف. بيستبدل النص الافتراضي.
returnUrlمفيش. الصفحة بتتعرض من غير زر عودة.لما يتظبّط، الصفحة بتعرض زر "العودة لـ اسم نشاطك التجاري" بيوصّل للرابط ده.

redirect مينفعش يكون متقدّم لما type: "hosted_confirmation". إرسال الاتنين بيتم رفضه.

cancelUrl: تبعت العميل فين عند فشل الدفع

cancelUrl هو الرابط اللي XPay بيحوّل ليه العميل لما تفشل محاولة دفع على صفحة الدفع المستضافة. اختياري، ومنفصل عن afterCompletion، وصالح بس لما uiMode: "hosted".

{
  "afterCompletion": { "type": "redirect", "redirect": { "url": "..." } },
  "cancelUrl": "https://yourshop.example/checkout/declined",
  "lineItems": [ ... ]
}

اللي بيطلّق التحويل لـ cancelUrl:

  • بطاقة اترفضت.
  • 3D Secure اترفض، أو اتلغى، أو خلصت مهلته.
  • وسيلة دفع محلية (فوري، ڤاليو، إلخ) خلصت مهلتها أو اترفضت من المعالج.
  • المعالج رجّع خطأ.

اللي ما بيطلّقش التحويل لـ cancelUrl:

  • العميل بيقفل التبويب. مفيش خطّاف إلغاء من ناحية العميل.
  • العميل بيضرب زر الرجوع. الصفحة المستضافة ما فيهاش زر "إلغاء" أو "رجوع".
  • الجلسة بتنتهي صلاحيتها وهي مفتوحة. ده بيعرض حالة نهائية "خلصت كل حاجة هنا" على الصفحة المستضافة بس.

لو cancelUrl متسابش، العميل بيشوف الفشل داخل الصفحة المستضافة ويقدر يحاول تاني هناك. تظبيطه بيديك فرصة تسجّل المحاولة، أو تعرض تجربة إعادة محاولة مخصّصة، أو تقدّم بديل زي طريقة دفع تانية.

وإيه بخصوص Drop-in وElements؟

afterCompletion هو نفس الحقل على كل uiMode، لكن السلوك وقت التشغيل بيتغير.

uiMode: hosted

صفحة XPay المستضافة بتنقل المتصفح لـ redirect.url (أو بتعرض صفحة التأكيد). السيرفر بتاعك ما بيشغّلش حاجة من ناحية العميل. cancelUrl بيشتغل هنا.

uiMode: embedded (Drop-in)

الـ SDK بيفتح الدفع المستضاف في iframe. عند النجاح، الـ SDK بيطلّق الـ callback بتاع onComplete بتاعك بالـ redirectUrl المستخرج من afterCompletion.redirect.url. الكود بتاعك بيقرّر يتنقّل، ولا يقفل النافذة، ولا يسلّم لصفحة النجاح بتاعتك. cancelUrl بيتم رفضه عند إنشاء الجلسة.

uiMode: custom (Elements)

الكود بتاعك بيستدعي confirmPayment() وبيتعامل مع نتيجة النجاح/الفشل بنفسه. afterCompletion لسه بيتنقل مع الجلسة (وبيظهر في حمولة الـ webhook)، لكن XPay ما بيتنقّلش ولا بيعرض أي حاجة. cancelUrl بيتم رفضه عند إنشاء الجلسة.

حمولة الجلسة اللي بتوصل في checkout.session.completed بتحمل نفس حقول afterCompletion بغض النظر عن uiMode، فكود تنفيذ الطلب على ناحية السيرفر بيبقى متطابق عبر الأنماط.

الـ webhook هو مصدر الحقيقة

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

  • العملاء بيقفلوا تبويبات. الـ redirect عمره ما بيتطلق.
  • الشبكات بتقع. الـ redirect بيوصل، لكن صفحة العودة بتاعتك مش قادرة توصل للـ backend بتاعك.
  • الناس بتضرب روابط بالغلط. أي حد يقدر يصنع رابط عودة بـ cs_* قديم.

اعتبر صفحة العودة (أو صفحة التأكيد المستضافة) مجاملة لتجربة الاستخدام، ومعالِج webhook الـ checkout.session.completed بتاعك هو مصدر الحقيقة الوحيد لتنفيذ الطلب. حمولة الـ webhook بتحمل جلسة الدفع كاملة، بما فيها الـ paymentIntent، والـ customer، والـ lineItems المستقرّين. شوف الـ Webhooks ← إعداد نقطة نهاية لوصفة المعالِج.

لو عايز تعرض الطلب الفعلي على صفحة العودة بتاعتك، اقراه برجوع بـ GET /checkout/sessions/:id من المسار (على فرض إنك استخدمت قالب {CHECKOUT_SESSION_ID}).

رايح فين بعد كده

في الصفحة دي